Effective Java - Item 9

try-finally보다는 try-with-resources를 사용하라

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

자원 닫기는 클라이언트가 놓치기 쉬워서 예측할 수 없는 성능 문제로 이어지기도 한다. 이런 자원 중 상당 수가 안전망으로 finalizer를 활용하고는 있지만 믿을 만하지 못하다.(아이템 8)

전통적으로 자원이 제대로 닫힘을 보장하는 수단으로 try-finally가 쓰였다.

static String fisrtLineOfFile(String path) throws IOException {

  BufferedReader br = new BufferedReader(new FileReader(path));
  try {
    return br.readLine();
  } finally {
    br.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 블록과 finally 블록 모두에서 발생할 수 있는데, 만약 기기에 물리적인 문제가 생긴다면 firstLineOfFile 메서드 안의 readLine 메서드가 예외를 던지고, 같은 이유로 close 메서드도 실패할 것이다.
  • 이러한 상황이라면 두 번째 예외가 첫 번째 예외를 집어삼켜 버린다.
  • 그러면 스택 추적 내역에 첫 번째 예외에 관한 정보는 남지 않게 되어, 실제 시스템에서의 디버깅을 몹시 어렵게 한다.(일반적으로 문제를 진단하려면 처음 발생한 예외를 보고 싶을 것이다.)
  • 두 번째 예외 대신 첫 번째 예외를 기록하도록 코드를 수정할 수는 있지만, 코드가 너무 지저분해져서 실제로 그렇게까지 하는 경우는 거의 없다.

해결! -> 자바 7이 투척한 try-with-resources

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

  • 단순히 void를 반환하는 close 메서드 하나만 덩그러니 정의한 인터페이스다.
  • 자바 라이브러리와 서드파티 라이브러리들의 수많은 클래스와 인터페이스가 이미 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)
  )
}

try-with-resources 버전이 짧고 읽기 수월할 뿐 아니라 문제를 진단하기도 훨씬 좋다.

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

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

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를 사용하자.
  • 예외는 없다. 코드는 더 짧고 분명해지고, 만들어지는 예외 정보도 훨씬 유용하다.
  • 정확하고 쉽게 자원을 회수할 수 있다.

Written by@Myunghwan
Nothing changes if nothing changes

GitHub