아이템 9. try finally 보다는 try with resources를 사용하라. - ksw6169/effective-java GitHub Wiki

개요

  • 자바 라이브러리에는 close() 를 호출해 직접 닫아줘야 하는 자원이 많다. (InputStream, Connection 등)
  • 자원 닫기는 클라이언트가 놓치기 쉬워서 예측할 수 없는 성능 문제로 이어지기도 한다.
  • 이러한 자원 중 상당수가 안전망으로 finalizer 를 활용하고는 있지만 finalizer 는 그리 믿을만하지 못하다.
  • 전통적으로는 자원이 제대로 닫히도록 보장하는 수단으로 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();
    }
}
  • 또한 예를 들어 기기에 물리적인 문제가 발생해 firstLineOfFile() 에서 readLine()close() 양쪽에서 예외가 발생하면 close() 에서 발생한 두 번째 예외가 첫 번째 예외를 완전히 집어삼켜 버린다. 그러면 스택 추적 내역에 첫 번째 예외에 관한 정보는 남지 않게 되어 실제 시스템에서의 디버깅을 몹시 어렵게 한다. (실제 디버깅할 때 중요한 예외는 첫 번째 예외이므로)
  • 이러한 문제들은 Java 7에 도입된 try-with-resources 구문으로 모두 해결되었다.

AutoCloseable

  • try-with-resources 구조를 사용하려면 해당 자원이 AutoCloseable 인터페이스를 구현해야 한다.
  • 자바, 서드파티 라이브러리들의 수많은 클래스와 인터페이스가 이미 AutoCloseable 을 구현하거나 확장해뒀다.
  • 닫아야 하는 자원 클래스에는 반드시 AutoCloseable 을 구현하라.
public interface AutoCloseable {
    void close() throws Exception;
}

try-with-resources

  • 아래는 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 에서도 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-with-resources의 장점

  • 가독성이 좋고 문제를 진단하기도 훨씬 좋다.

  • 중요한 예외 하나만 보존되고 여러 개의 다른 예외가 숨겨질 수 있다.

    • 앞의 예제 firstLineOfFile() 에서 readLine()close() 양쪽에서 예외가 발생하면 close() 에서 발생한 예외는 숨겨지고 readLine() 에서 발생한 예외가 기록된다. 여기서 숨겨진 예외들은 버려지지 않고 스택 추적 내역에 '숨겨졌다(suppressed)' 는 꼬리표를 달고 출력된다. 이는 Java 7의 Throwable 에 추가된 getSuppressed() 를 이용하면 코드에서 가져올 수도 있다.

참고 자료

  • Effective Java 3/E