[TIL] Java Exception

revo·2026년 4월 13일

자바

목록 보기
21/30
post-thumbnail

예외는 클래스 상속 계층으로 설계되어 있다

Throwable 계층 구조

Object
  └── Throwable
        ├── Exception (S/W)
        │     ├── CheckedException
        │     │     ├── FileNotFoundException
        │     │     ├── IOException
        │     │     └── SQLException
        │     │     └── InterruptedException
        │     └── RuntimeException (UncheckedException)
        │           ├── ArrayIndexOutOfBoundsException
        │           ├── NegativeArraySizeException
        │           ├── NullPointerException
        │           ├── ClassCastException
        │           ├── NumberFormatException
        │           ├── IndexOutOfBoundsException
        │           ├── ArithmeticException
        │           ├── IllegalArgumentException      
        └── Error (System)
              ├── OutOfMemoryError   // heap 메모리 부족
              ├── StackOverflowError // Stack 메모리 부족
              └── NoSuchMethodError  // main()이 없는데 수행한 경우

주요 Exception 목록

Exception설명종류
ArrayIndexOutOfBoundsException배열의 접근 범위를 벗어난 경우Unchecked
NegativeArraySizeException배열의 크기를 음수로 지정한 경우Unchecked
NullPointerException객체를 선언만 하고 생성하지 않은 상태에서 접근한 경우Unchecked
ClassCastExceptionReference 타입의 잘못된 형변환 시 발생Unchecked
NumberFormatException문자열로된 데이터를 Primitive로 변환 시 잘못 변경하면 발생Unchecked
IndexOutOfBoundsExceptionList에서 잘못된 index에 데이터를 추가한 경우Unchecked
ArithmeticException0으로 나누는 경우Unchecked
IllegalArgumentException메서드에 잘못된 인자를 전달한 경우Unchecked
InterruptedException쓰레드 수행중 중단 명령에 따른 오류Checked
FileNotFoundException지정한 경로에 파일이 없는 경우Checked
IOException데이터를 IO하는 중 발생하는 오류Checked
SQLExceptionDB 서버에서 데이터 처리 중 발생하는 오류Checked

여기서 ErrorException은 다르다. ErrorStackOverflowError, OutOfMemoryError처럼 JVM 자체의 심각한 문제다. 개발자가 처리하는 것이 의미 없거나 불가능한 수준이므로, 잡으려 하지 않는 것이 맞다. 우리가 실질적으로 다루는 영역은 Exception이다.


Checked와 Unchecked — 왜 나눴는가

Exception은 두 종류로 나뉜다. 기준은 RuntimeException의 상속 여부다.

Checked ExceptionRuntimeException을 상속하지 않는 Exception이다. 컴파일러가 처리를 강제한다. try-catchthrows 없이 사용하면 빌드 자체가 실패한다.

Unchecked ExceptionRuntimeException을 상속하는 Exception이다. 컴파일러가 강제하지 않는다. 컴파일은 통과하고, 런타임에서 발생한다.

"컴파일 안 됨"과 "실행 안 됨"은 다르다. Checked Exception을 처리하지 않으면 빌드 단계에서 막힌다. Unchecked Exception은 빌드는 되고 실행 중에 터진다. 시점이 완전히 다르다.

이 구분이 생긴 이유가 중요하다. Unchecked 계열은 대부분 개발자 코드의 실수에서 발생한다. null을 잘못 다루거나, 배열 범위를 잘못 계산하거나. 이런 건 컴파일러가 강제하는 게 아니라 코드 자체를 올바르게 짜야 하는 문제다. 반면 Checked 계열인 IOException, SQLException은 파일이 존재하는지, DB 연결이 되는지 같은 외부 환경에 의존한다. 코드를 아무리 잘 짜도 파일이 없을 수 있다. 그래서 컴파일러가 "이 상황에 대한 처리 코드를 반드시 작성해"라고 강제하는 것이다.


try-catch-finally 실행 흐름

try {
    // 예외가 발생할 수 있는 코드
} catch (예외타입 변수명) {
    // 예외 발생 시 처리
} finally {
    // 예외 발생 여부와 무관하게 항상 실행
}
public class ExceptionHandling {
	public static void main(String[] args) {
		int[] scores = { 1, 2, 3 };
		try {
			System.out.println(scores[5]);
			System.out.println("이 줄은 실행 안됨");
		} catch (ArrayIndexOutOfBoundsException e) {
			System.out.println("배열 범위 초과" + e.getMessage());
		} finally {
			System.out.println("finally는 항상 실행");
		}
	}

}
출력 > 배열 범위 초과Index 5 out of bounds for length 3
finally는 항상 실행

흐름은 단순하다. try 블록 실행 중 예외가 발생하면, 발생 지점 이후 코드는 건너뛰고 해당 예외 타입과 일치하는 catch 블록으로 이동한다. finally는 예외 발생 여부와 관계없이 무조건 실행된다.

다중 catch를 쓸 때는 자식 타입을 먼저 써야 한다.

try {
    int[] arr = {1, 2, 3};
    System.out.println(arr[10]);
} catch (ArrayIndexOutOfBoundsException e) {  // 자식 먼저
    System.out.println("배열 범위 초과");
} catch (RuntimeException e) {                 // 부모 나중에
    System.out.println("그 외 런타임 예외");
}

부모 타입을 먼저 쓰면 자식이 절대 잡히지 않는다. 컴파일러가 이걸 에러로 잡아준다.

여기서 앞서 배운 다형성이 그대로 적용된다. catch에 부모 타입을 쓰면 자식 예외들을 전부 잡을 수 있다. catch (RuntimeException e)RuntimeException의 모든 자식 예외를 업캐스팅해서 받는 것이다.


throw vs throws

이 둘은 다르다.

throw는 메서드 내부에서 예외 객체를 직접 생성해서 던지는 것이다.

throw new IllegalArgumentException("잘못된 값");

throws는 메서드 선언부에 붙이는 것으로, 이 메서드가 해당 예외를 던질 수 있다고 선언하고 처리를 호출자에게 위임하는 것이다.

public void readFile(String path) throws IOException {
    FileReader fr = new FileReader(path);
}

throws를 선언한 메서드를 호출하는 쪽에서는 반드시 처리해야 한다. Checked Exception에 한해서다. Unchecked는 throws 없이도 예외가 위로 전파된다.


사용자 정의 예외 — 상속으로 만든다

도메인에 맞는 예외를 직접 설계할 수 있다. Checked로 만들려면 Exception을, Unchecked로 만들려면 RuntimeException을 상속하면 된다.

public class InvalidSalaryException extends RuntimeException {
    public InvalidSalaryException(String message) {
        super(message);  // RuntimeException 생성자에 메시지 전달
    }
}
// 급여가 음수면 예외를 던지는 예시
public void setSalary(int salary) {
    if (salary < 0) {
        throw new InvalidSalaryException(name + "의 급여가 음수입니다: " + salary);
    }
    this.salary = salary;
}

정리

개념핵심
ErrorJVM 레벨 문제, 처리 불가
Checked Exception컴파일러 강제, 외부 환경 의존
Unchecked Exception강제 없음, 개발자 코드 실수
throw메서드 내부에서 예외 객체를 직접 던짐
throws예외 처리를 호출자에게 위임
finally예외 발생 여부 무관하게 무조건 실행
다중 catch 순서자식 타입 먼저, 부모 타입 나중에
사용자 정의 예외Exception 또는 RuntimeException 상속

0개의 댓글