[JAVA] 예외 처리
프로그램의 실행 오류를 발생 시점에 따라 다음과 같이 나눌 수 있다.
- 컴파일 에러 - 실행 전
- 런타임 에러 - 실행 중
- 논리적 에러 - 컴파일도 잘 되고 실행도 잘 되지만 의도한 것과 다르게 동작하는 것
위와 같이 실행 중 발생하는 오류(런타임 에러)는 다시 에러(error)와 예외(exception)로 구분할 수 있다.
- 에러 - 심각한 오류로 복구 불가능
- 예외 - 덜 심각한 오류로 복구 가능
그래서 예외를 처리한다고 할 때 수습하기 어려운 에러를 다루는 것이 아니라 그나마 덜 심각한 오류인 예외를 대비함으로써 프로그램이 종료되지 않도록 한다. try-catch 문에서 catch(Exception e) {} 로 조건식을 정의하면 실행 중 발생하는 모든 예외를 받을 수 있다.
예외(exception)는 또 다시 두 가지로 나누어 생각할 수 있다.
- 오류 (error)
- 예외 (exception)
- Exception 클래스와 그 자손 (checked)
- 주로 외부의 영향으로 발생한다
- 사용자 실수와 같은 외적인 요인에 의해 발생, 예외 처리 필수
- FileNotFoundException, ClassNotFoundException, DataFormatException ... - RuntimeException 클래스와 그 자손 (unchecked)
- 주로 자바의 프로그래밍 요소들과 관계가 깊다
- 프로그래머의 실수로 발생, 예외 처리 선택
- ArrayIndexOutOfBoundsException, NullPointerException, ClassCastException, ArithmeticException ...
- Exception 클래스와 그 자손 (checked)
예외가 발생하면 예외 객체가 생성되고 이 안에는 발생한 문제에 대한 정보를 가지고 있다. 대표적으로 printStackTrace(), getMessage()로 예외 정보를 확인한다. 예외를 처리하는 방법은 크게 3+1개가 있다.
- 직접 처리: try catch문으로 처리하고 catch 문에서 예외를 받으면 예외가 없어져 정상 종료된다
- 보고: 예외 선언(호출한 쪽으로 떠넘기기)
- 메서드 선언부에 throws ~
- 예외를 호출부로 계속 떠넘기며 처리를 하지 않으면 나중에 JVM에서 처리한다 - 은폐(덮기) : 빈 catch 블록
- 예외 되던지기: 1번과 2번이 결합된 형태로 직접 처리도 하고 위에 예외를 다시 던지는 형태
JDK1.7 이후부터 멀티 블록이 추가되어서 catch 블록에서 예외를 함께 받을 수 있도록 했다.
catch(Exception A | Exception B) {}
예외 개수는 상관 없지만 두 가지의 주의 사항이 있다.
- 부모-자식 관계가 아니어야 한다.
- 생각해 보면 부모 타입은 자식 타입을 다 받을 수 있기 때문에 두 가지를 함께 선언하는 것은 불필요하다. - 공통 멤버만 사용할 수 있다.
- 하나의 참조변수에 어떤 예외 객체가 들어오는지 알 수 없기 때문에 둘의 공통 멤버만 사용할 수 있다.
멀티 블록으로 받는다는 것 자체가 어떤 예외들에 대해 동일한 예외 처리를 해 준다는 것이기 때문에 이 안에서 굳이 예외를 구분해 처리하는 것은 효율적이지 못 하다. 따로 처리해 줄 것이라면 개별적으로 catch 블록을 만드는 게 나을 수 있다.
모든 예외는 다른 예외를 포함할 수 있게 한다. 예시와 함께 이해해 보자. 예를 들어 예외 A가 발생해서 이를 처리하다가 또 다른 예외 B가 발생한 상황이다. 이 경우 가장 먼저 발생한 예외를 실제 발생한 예외로 인식하고 그 아래 Suppressed로 억제된 예외, 즉 가장 최근에 발생한 예외를 함께 표시한다. - 포함
그리고 연결된 예외가 있다. 말 그대로 예외들이 연결되어 있다는 것인데, 어떤 예외 A가 발생할 때 그에 대한 결과로 예외 B가 발생하는 상황이다. 이때 예외 A는 예외 B에 대한 원인 예외가 된다.
연결된 예외를 사용하는 이유는 다음과 같다.
- 대략/포괄적인 정보를 먼저 보여준 다음 세부 정보를 보여주기 위해서
처음부터 MemoryException, SpaceException 등으로 직접적인 예외 발생 원인을 먼저 알리는 게 아니라 추상적인 InstallException으로 먼저 알리는 것이다. 이렇게 하면 InstallException으로 '설치 과정에서 발생한 오류구나' → '이에 대한 원인은 메모리 부족 문제구나' 하고 상황을 파악하기가 더 수월해진다.
- checked 예외를 unchecked 예외로 처리하기 위해서
앞서 언급했듯 Exception 예외와 그 자손 클래스들은 checked 예외로서 예외 처리를 필수로 해야 한다. 하지만 프로그래밍 환경이 변화함으로써 과거에 필수로 예외 처리를 해 주었던 것들이 이제는 unchecked로 처리하는 것이 더 낫다고 생각될 때도 있다. 그럴 때 checked 예외를 unchecked 예외로 감싸면 예외 처리를 선택적으로 할 수 있게 된다.