Effective Java - 아이템 9: try-finally보다는 try-with-resources를 사용하라

1 분 소요

try-finally

전통적으로 예외가 발생하거나 메서드에서 반환되는 경우를 포함해 자원이 제대로 닫힘을 보장하는 수단으로 try-finally가 쓰였다.

// 더 이상 자원을 회수하는 최선의 방책이 아니다.
static String firstLineOfFile(String path) throws IOException {
    BufferedReader br = new BufferedReader(new FileReader(path));
   try {
       return br.readLine();
   } finally {
       br.close();
   }
}
// 자원이 둘 이상이면 try-finally 방식은 너무 지저분하다.
static void copy(String src, String dst) throws IOException {
   InputStream in = new FileInputStream(src);
   try {
      OutputStream out = new FileoutputStream(dst);
      try {
         byte[] buf = new byte[BUFFER_SIZE];
         int n;
         while ((n = in.read(buf)) >= 0) {
            out.write(buf, 0, n);
         }
      } finally {
         out.close();
      }
   } finally{
      in.close();
   }
}

위 코드에서 예외는 try 블록과 finally 블록 모두에서 발생할 수 있는데,
안과 밖에서 두 개의 예외가 발생할 경우 두 번째 예외가 첫 번째 예외를 완전히 집어삼켜 버린다.
그러면 스택 추적 내역에 첫 번째 예외에 관한 정보는 남지 않게 되어 디버깅을 어렵게 한다.

try-with-resources

위와 같은 문제를 try-with-resources를 사용하여 모두 해결할 수 있다.
이 구조를 사용하려면 해당 자원이 AutoCloseable 인터페이스를 구현해야 한다.
AutoClosable 인터페이스는 void를 반환하는 close 메서드 하나만 정의되어 있다.
닫아야 하는 자원을 뜻하는 클래스를 작성한다면 AutoCloseable을 구현하라.

// 위의 코드를 try-with-resources로 재작성
static String firstLineOfFile(String path) throws IOException {
   try (BufferedReader br = new BufferedReader(new FileReader(path))) {
       return br.readLine();
   }
}
static void copy(String src, String dst) throws IOException {
   try (InputStream in = new FileInputStream(src);
        OutputStream out = new FileOutputStream(dst)) {
      byte[] buf = new byte[BUFFER_SIZE];
      int n;
      while ((n = in.read(buf)) >= 0) {
         out.write(buf, 0, n);
      }
   }
}

try-with-resources를 사용한 코드가 가독성이 훨씬 좋다.
firstLineOfFile 메서드에서 readLine과 코드에는 나타나지 않은 close 호출 양쪽에서 예외가 발생하면,
close에서 발생한 예외는 숨겨지고 readLine에서 발생한 예외가 기록된다.
이처럼 실전에서는 프로그래머에게 보여줄 예외 하나만 보존되고 여러 개의 다른 예외가 숨겨질 수도 있다.
하지만 숨겨진 예외들은 버려지지 않고 스택 추적 내역에 숨겨졌다(suppressed)는 꼬리표를 달고 출력된다.
자바 7에서 Throwable에 추가된 getSuppressed 메서드를 이용하면 프로그램 코드에서 가져올 수도 있다.

catch 절

보통의 try-finally에서처럼 try-with-resources에서도 catch 절을 사용해 try문을 중첩하지 않고도 다수의 예외를 처리할 수 있다.

// try-with-resources를 catch절과 함께 쓰는 모습
// 파일을 열거나 데이터를 읽지 못했을 때 예외를 던지는 대신 기본값을 반환하는 예시 코드
static String firstLineOfFile(String path, String defaultVal) {
   try (BufferedReader br = new BufferedReader(new FileReader(path))) {
      return br.readLine();
   } catch (IOException e) {
      return defaultVal;
   }
}

핵심 정리

꼭 회수해야 하는 자원을 다룰 때는 try-finally 말고, try-with-resources를 사용하자.
try-finally로 작성하면 지저분해지는 코드도 try-with-resources로는 정확하고 쉽게 자원을 회수할 수 있다.

댓글남기기