[9주차] 예외처리

목차

  • 자바에서 예외 처리 방법 (try, catch, throw, throws, finally)
  • 자바가 제공하는 예외 계층 구조
  • Exception과 Error의 차이는?
  • RuntimeException과 RE가 아닌 것의 차이는?
  • 커스텀한 예외 만드는 방법

1. 자바에서 예외 처리 방법 (try, catch, throw, throws, finally)

1.1 예외 복구 : try ~ catch (~ finally)

  • 예외가 발생하면 JVM은 즉시 프로그램을 중단하고, 해당 예외가 등록된 클래스를 객체로 만들어 Throw한다.

  • 이 때, JVM이 throw한 예외를 잡는 것이 try ~ catch구문이다.

try {
    // 예외가 발생할 가능성이 있는 코드
} catch (Exception1 e) {
    // Exception1이 발생했을 때 처리하는 코드
} finally {
  // 예외가 발생하던 안하던 무조건 실행되는 코드
}
  • 예외가 발생한 경우와 발생하지 않은 경우 처리 흐름이 다르다.

  • 예외가 발생하면 JVM이 즉시 프로그램 진행을 멈추기 때문이다.

image

1.2 예외처리 회피

  • 예외처리를 자신이 담당하지 않고 자신을 호출한 쪽으로 던져버리는 것이다.

  • 보통 throws문으로 선언하거나 catch문에서 rethrow한다.

1.3 예외 전환

  • 예외 상황에 대해 의미가 좀 더 명확한 예외로 재정의하여 throws한다.

  • 보통 전환하려는 예와에 원래의 예외를 담는 중첩 예외를 만들어서 예외를 던지는 것이 좋다.

1.4 throw와 throws

  • throw와 throws는 모두 예외를 던지는 것이다.

  • 다만 throw는 Exception을 발생시킬 때 사용하는 키워드이며, 최상위 예외 클래스인 Exeption에 대해서 예외처리를 하면 거의 모든 예외가 잡힌다.

  • throws는 메소드를 정의할 때 사용하며, 메소드에서 발생하는 Exception의 종류를 명시적으로 정의할 때 주로 사용한다.

1.5 finally에서의 return

  • finally에서 return값을 던지는 것은 안티패턴이다.

  • 만일 try, catch 문에 return이 있어도 finally문에 return 이 있다면 finally의 return만 수행될 수 있기 때문이다.

1.6 멀티 Catch 블록

  • 아래와 같이 오류를 처리한다면 예외처리 안에서 중복 코드들이 발생하는 문제가 있다.
// JDK 1.7 이전
try {

} catch (Exception1 e1) {
    // 예외처리1
} catch (Exception2 e2) {
    // 예외처리2
.
.
.
} catch (ExceptionN eN) {
    // 예외처리n
}
  • 이러한 문제를 해결하기 위해 JDK 1.7부터는 멀티캐치문(|)을 제공하고 있다.
    • 이때, 다형성에 의해 부모 예외로 자손 예외를 처리할 수 있기 때문에 부모-자식 관계의 예외 클래스는 멀티캐치 블록을 사용할 수 없다.
// JDK 1.7이상
try{
 
}catch(Exception1 e1 | Exception2 e2| ExceptionN eN){
   //공통 예외로직 ex)e.printStackTrace();
}

1.7 try ~ with ~ resources

  • 입출력을 수행하는 도중 예외가 발생하면 입출력을 수행하던 stream이 open되어진 상태로 남겨지고, close되지 않은 스트림이 쌓여 자원부족으로 프로그램이 멈추게된

  • 메모리가 점점 고갈되는 현상을 메모리 누수(Memory Leak)이라고 한다.

  • 이러한 예외를 처리하기 위해 도입된 것이 try ~ with ~ resources 문이다.

  • 만일, 기존 방식으로 예외처리를 하게 되면 아래와 같이 소스가 복잡해지기 때문에 JDK 1.7부터 생긴 기능이다.

FileOutputStream out = null; 

try { 
  out = new FileOutputStream("exFile.txt"); //...이후 입출력 로직 처리... 
} catch (FileNotFoundException e) { 
  e.printStackTrace(); 
} finally { 
  if(out != null) { 
    //스트림이 null인지 체크 
    try { 
      out.close(); //close 하다가 예외가 발생할 수 있다. 
    } 
    catch (IOException e) { 
       e.printStackTrace(); 
    } 
  } 
}
  • try with resources를 사용하게 되면 예외가 발생 시, 컴파일 시 자동으로 catch문장에서 close()를 호출 후 이후 작업이 수행되거나 예외가 없으면 그냥 바로 close가 호출된다.

  • 만약 finally에서 직접 close를 호출하는 코드를 사용하고 있다면 try with resorce로 변경해주는 것을 추천한다.

try(FileOutputStream out = new FileOutputStream("exFile.txt")) { 
  //...이후 입출력 로직 처리... 
} catch(IOException e){ 
  e.printStackTrace(); 
}
  • close()메서드 호출 후, 예외를 반납하던 중에 예외가 발생하면 CloseException이다.

  • try with resouces를 통해 자동으로 자원을 반납하기 위해서는 AutoCloseable 인터페이스를 구현해야 한다.

public interface AutoCloseable { 
  void close() throws Exception; // 예외발생 시, JVM이 close()메소드를 호출한다
}

2. 자바가 제공하는 예외 계층 구조

image

  • Throwable은 모든 예외의 조상이 되는 Exception클래스와 모든 오류의 조상이 되는 Error 클래스의 부모 클래스이다.

  • Throwable에는 아래 메소드들이 포함되어 있다.
    • void printStackTrace() : throwable 객체와 Standard Error Stream에서 해당 객체의 Stack Trace를 출력한다. 즉, 예외가 발생하게 되면 Stack 메모리 영역에 저장된 Stack Frame을 JVM이 Pop하면서 Stack Trace를 출력한다.

    • String getMessage() : throwable 객체에 대한 자세한 내용을 문자열로 반환한다.

    • String toString() : 해당 throwable 객체에 대한 간략한 내용을 문자열로 반환한다.

  • Exception은 크게 RuntimeException와 Execption로 나뉜다.

2.1 RuntimeException과 기타 Execption

2.1.1 RuntimeException

  • RuntimeException은 Java Virtual Machine의 정상적인 작동 중에 발생할 수 있는 예외의 슈퍼 클래스이다.

  • RuntimeException 및 해당 서브 클래스는 unchecked exception이다.

  • unchecked exception은 메서드 또는 생성자의 실행에 의해 발생한다.

  • 명시적 예외처리를 꼭 할 필요는 없다.

  • RuntimeException 클래스

    • ArrayIndexOutOfBoundException: 배열에서 존재하지 않는 인덱스 가리킬때

    • ArithmeticException: 정수를 0으로 나누려고 하는 경우

    • ClassCastException: 클래스간의 형변환을 잘못한 경우

    • NumberFormatException: 숫자데이터 문제데이터 등을 넣었을 때

    • NullPointerException: null 객체의 레퍼런스를 이용하거나 null 객체의 인스턴스 메소드를 호출하는 등

2.1.2 기타 Execption

  • 기타 Execption은 대부분 checked exception이다.
  • checked exception은 반드시 예외 처리를 해야 한다. (try~catch를 통해)

  • checked exception은 컴파일 단계에서 확인 가능하다.

  • RuntimeException을 제외한 Exception의 하위 클래스이다.

  • Execption클래스
    • ClassNotFoundException: 실수로 클래스의 이름을 잘못 적은 경우

    • DataFormatException: 입력한 데이터 형식이 잘못된 경우

    • FileNotFoundException: 존재하지 않는 파일의 이름 입력 시

3. Exception과 Error의 차이는?

3.1 Error

  • Error는 주로 JVM이나 하드웨어 등 기반 시스템의 문제로 발생한다.

  • 발생하는 순간 프로그램이 비정상적으로 종료되며, 개발자가 Error를 처리할 수 없다.

3.1.1 컴파일에러

  • 컴파일 과정에서 일어나는 에러

  • 자바 컴파일러가 오류를 검사해서 알려준다.

  • 하지만 컴파일 오류를 수정해도 runtime에러가 발생하는 경우가 있다.

3.1.2 런타임에러

  • 실행 과정에서 일어나는 에러

  • 대개 런타임에러는 Exception과 Error 두 가지로 나뉜다.

3.2 Exception

  • Exception은 프로그래머가 미리 발생할 것을 예측하고 방지할 수 있다.

4. 커스텀한 예외 만드는 방법

  • 커스텀한 예외처리는 throw에 직접 작성한 클래스 파일을 지정하는 것이다.

  • 커스텀한 예외를 만드는 것보다 IllegalArgumentException 이나 UnsupportedOperationException을 사용하는 것이 더 나을때가 많다.

  • 정말 꼭 필요한건지 커스텀한 예외를 만들기 전에 꼭 고려해보자

  • 커스텀 예외를 던질때 catch문에서 꼭

5. 연결된 예외(Exception chaining), Java8

5.1 Exception chaining이란?

  • 한 예외가 다른 예외를 발생시킬 수 있다.

  • 예외 A가 예외 B를 발생시키면, A는 B의 원인 예외(Cause Exception)이다.

  • 이때 A와 B를 연결시키는 것을 연결된 예외라고 한다.

  • 예외 안에 또 다른 예외를 포함시키는 것이 연결된 예외라고 볼 수 있다.

  • Throwable 클래스의 생성자가 이런 연쇄적 예외 기능을 지원한다.

Method Description
Throwable getCause() Returns the original cause of the exception
Throwable initCause(Throwable cause) Sets the cause for invoking the exception
public class Example {
   public static void main(String[] args) {
      try {
         // 새로운 예외 객체 e를 생성한다.
         ArithmeticException e = new ArithmeticException("Apparent cause");
         // 지정된 예외를 원인 예외로 등록한다.
         e.initCause(new NullPointerException("Actual cause"));
         // 예외를 던진다.
         throw e;
      } catch(ArithmeticException e) {
         // 예외의 지정된 원인 예외를 반환해서 출력한다.
         System.out.println(e.getCause());
         // 출력결과 : java.lang.NullPointerException: Actual cause
      }
   }
}

5.2 연결된 예외를 사용하는 이유

  • 여러 예외를 하나로 묶어서 다루기 위해서이다.
    • 이는 예외에 대한 좀 더 상세한 설명을 예외 시 보여주고 싶을 때 유용하다.
void install() throws InstallException {
    try {
        startInstall();     // SpaceExeption 발생
        copyFiles();
    } catch (SpaceException e) {
        InstallException ie = new InstallException("설치중 예외발생");     // 예외 생성
        ie.initCause(e);    // InstallException의 원인 예외를 SpaceException으로 지정
        throw ie;
    } catch (MemoryExeption me) {
        /* ...... */
    }
}
  • checked 예외를 unchecked예외로 변경하려할 때 사용한다.
    • 즉, 필수처리 예외를 선택처리 예외로 바꾸고 싶을 때 사용한다.
    • RuntimeException을 만들고 그 안에 원인 예외로 등록하면 된다.
    • 이 때는 initCause 메소드가 아니라 생성자를 사용한다.
static void startInstall() throws SpaceExeption {
    if(!enoughSpace())
        throw new Exception("설치할 공간이 부족합니다");
    if(!enoughMemory())
        throw new RuntimeException(new MemoryExeption("메모리가 부족합니다"));
}

출처