[이펙티브 자바] 아이템 9. try-finally보다는 try-with-resources를 사용하라

이펙티브 자바를 읽고 정리한 글입니다.

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


자바 라이브러리에는 InputStream, OutputStream, java.sql.Connection 등 close 메서드를 호출해 직접 닫아줘야 하는 자원이 많다.

  • 자원 닫기는 클라이언트가 놓치기 쉬워서 예측할 수 없는 성능 문제로 이어지기도 한다.
  • 안전망으로 finalizer를 활용하고는 있지만 finalizer는 그리 믿을만하지 못하다.

전통적으로 자원이 제대로 닫힘을 보장하는 수단으로 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 블록 모두에서 발생할 수 있다.

  • 만약 기기의 물리적인 문제로 인해 readLine 메서드와 close 메서드에서 동시에 예외가 발생하는 경우, 두 번째 예외가 첫 번째 예외를 완전히 집어삼켜 버린다.
  • 그러면 스택 추적 내역에 첫 번째 예외에 관한 정보가 남지 않게 되어, 실제 시스템에서의 디버깅을 몹시 어렵게 한다.

코드가 지저분해진다.

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


이러한 문제들을 해결하기 위해 자바 7에서 try-with-resources 투척했다!

public interface AutoCloseable {
    void close() throws Exception;
}

이 구조를 사용하려면 해당 자원이 AutoCloseable 인터페이스를 구현해야 한다.

  • 닫아야 하는 자원을 뜻하는 클래스를 작성한다면 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 버전이 짧고 읽기 수월할 뿐 아니라 문제를 진단하기도 훨씬 좋다.

  • readLineclose 호출 양쪽에서 예외가 발생하면, close에서 발생한 예외는 숨겨지고 readLine에서 발생한 예외가 기록된다.
  • 숨겨진 예외들은 그냥 버려지지 않고, 스택 추적 내역에 '숨겨졌다'는 꼬리표를 달고 출력된다.
  • 자바 7에서 Throwable에 추가된 getSuppressed 메서드를 이용하면 프로그램 코드에서 가져올 수도 있다.

보통의 try-finally에서처럼 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;
    }
}

catch 절 덕분에 try 문을 더 중첩하지 않고도 다수의 예외를 처리할 수 있다.

핵심 정리


  • 꼭 회수해야 하는 자원을 다룰 때는 try-finally 말고, try-with-resources를 사용하자. 예외는 없다.
  • 코드는 더 짧고 분명해지고, 만들어지는 예외 정보도 훨씬 유용하다.
  • try-finally로 작성하면 실용적이지 못할 만큼 코드가 지저분해지는 경우라도, try-with-resources로는 정확하고 쉽게 자원을 회수할 수 있다.

좋은 웹페이지 즐겨찾기