hail2y 2025. 1. 13. 13:16

 프로그램의 실행 오류를 발생 시점에 따라 다음과 같이 나눌 수 있다.

  • 컴파일 에러 - 실행 전
  • 런타임 에러 - 실행
  • 논리적 에러 - 컴파일도 잘 되고 실행도 잘 되지만 의도한 것과 다르게 동작하는 것

 위와 같이 실행 중 발생하는 오류(런타임 에러)는 다시 에러(error)와 예외(exception)로 구분할 수 있다. 

  • 에러 - 심각한 오류로 복구 불가능
  • 예외 - 덜 심각한 오류로 복구 가능

그래서 예외를 처리한다고 할 때 수습하기 어려운 에러를 다루는 것이 아니라 그나마 덜 심각한 오류인 예외를 대비함으로써 프로그램이 종료되지 않도록 한다. try-catch 문에서 catch(Exception e) {} 로 조건식을 정의하면 실행 중 발생하는 모든 예외를 받을 수 있다. 

https://www.geeksforgeeks.org/exceptions-in-java/
https://www.geeksforgeeks.org/exceptions-in-java/

예외(exception)는 또 다시 두 가지로 나누어 생각할 수 있다.

  • 오류 (error)
  • 예외 (exception)
    • Exception 클래스와 그 자손 (checked)
      - 주로 외부의 영향으로 발생한다
      - 사용자 실수와 같은 외적인 요인에 의해 발생, 예외 처리 필수
      - FileNotFoundException, ClassNotFoundException, DataFormatException ...
    • RuntimeException 클래스와 그 자손 (unchecked) 
      - 주로 자바의 프로그래밍 요소들과 관계가 깊다
      - 프로그래머의 실수로 발생, 예외 처리 선택
      - ArrayIndexOutOfBoundsException, NullPointerException, ClassCastException, ArithmeticException ...

예외가 발생하면 예외 객체가 생성되고 이 안에는 발생한 문제에 대한 정보를 가지고 있다. 대표적으로 printStackTrace(), getMessage()로 예외 정보를 확인한다. 예외를 처리하는 방법은 크게 3+1개가 있다. 

  1. 직접 처리: try catch문으로 처리하고 catch 문에서 예외를 받으면 예외가 없어져 정상 종료된다
  2. 보고: 예외 선언(호출한 쪽으로 떠넘기기)
    - 메서드 선언부에 throws ~
    - 예외를 호출부로 계속 떠넘기며 처리를 하지 않으면 나중에 JVM에서 처리한다
  3. 은폐(덮기) : 빈 catch 블록
  4. 예외 되던지기: 1번과 2번이 결합된 형태로 직접 처리도 하고 위에 예외를 다시 던지는 형태

JDK1.7 이후부터 멀티 블록이 추가되어서 catch 블록에서 예외를 함께 받을 수 있도록 했다. 

catch(Exception A | Exception B) {}

 

예외 개수는 상관 없지만 두 가지의 주의 사항이 있다. 

  • 부모-자식 관계가 아니어야 한다.
    - 생각해 보면 부모 타입은 자식 타입을 다 받을 수 있기 때문에 두 가지를 함께 선언하는 것은 불필요하다.
  • 공통 멤버만 사용할 수 있다.
    - 하나의 참조변수에 어떤 예외 객체가 들어오는지 알 수 없기 때문에 둘의 공통 멤버만 사용할 수 있다.

멀티 블록으로 받는다는 것 자체가 어떤 예외들에 대해 동일한 예외 처리를 해 준다는 것이기 때문에 이 안에서 굳이 예외를 구분해 처리하는 것은 효율적이지 못 하다. 따로 처리해 줄 것이라면 개별적으로 catch 블록을 만드는 게 나을 수 있다. 

 

모든 예외는 다른 예외를 포함할 수 있게 한다. 예시와 함께 이해해 보자. 예를 들어 예외 A가 발생해서 이를 처리하다가 또 다른 예외 B가 발생한 상황이다. 이 경우 가장 먼저 발생한 예외를 실제 발생한 예외로 인식하고 그 아래 Suppressed로 억제된 예외, 즉 가장 최근에 발생한 예외를 함께 표시한다. - 포함 

자바의 정석 3판 p.438

 

그리고 연결된 예외가 있다. 말 그대로 예외들이 연결되어 있다는 것인데, 어떤 예외 A가 발생할 때 그에 대한 결과로 예외 B가 발생하는 상황이다. 이때 예외 A는 예외 B에 대한 원인 예외가 된다. 

원인 예외 구조

 

연결된 예외를 사용하는 이유는 다음과 같다.

  • 대략/포괄적인 정보를 먼저 보여준 다음 세부 정보를 보여주기 위해서

처음부터 MemoryException, SpaceException 등으로 직접적인 예외 발생 원인을 먼저 알리는 게 아니라 추상적인 InstallException으로 먼저 알리는 것이다. 이렇게 하면 InstallException으로 '설치 과정에서 발생한 오류구나' →  '이에 대한 원인은 메모리 부족 문제구나' 하고 상황을 파악하기가 더 수월해진다. 

  • checked 예외를 unchecked 예외로 처리하기 위해서

앞서 언급했듯 Exception 예외와 그 자손 클래스들은 checked 예외로서 예외 처리를 필수로 해야 한다. 하지만 프로그래밍 환경이 변화함으로써 과거에 필수로 예외 처리를 해 주었던 것들이 이제는 unchecked로 처리하는 것이 더 낫다고 생각될 때도 있다. 그럴 때 checked 예외를 unchecked 예외로 감싸면 예외 처리를 선택적으로 할 수 있게 된다.