[Effective Java] 아이템 9 - try-finally 보다는 try-with-resources를 사용하라
자바 라이브러리에는 close 메서드를 호출해 직접 닫아줘야 하는 자원이 많다.
ex) InputStream, OutputStream, java.sql.Connection, ..
자원 닫기는 클라이언트가 놓치기 쉽기 때문에 예측할 수 없는 성능 문제로 이어지기도 한다.
이런 자원 중 상당수가 안전망으로 finalizer를 활용하고 있지만 믿을만 하지 못하다(아이템8 참고)
전통적으로 자원이 제대로 닫힘을 보장하는 수단으로 try-finally가 쓰였다.
try-finally
public static String firstLineOfFile(String path) throw 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 블록 모두에서 발생할 수 있다는 것이다.
(close 메서드를 호출할 때)
또한, try-finally를 중첩하는 경우 코드가 너무 지저분해져서 가독성이 떨어지게 된다.
해결책: try-with-resources
try-with-resources 를 사용하려면 해당 자원이 AutoCloseable 를 구현해야 한다.
(cf. AutoCloseable : 단순히 void를 반환하는 close 메서드 하나만 정의한 인터페이스)
닫아야 하는 자원을 뜻하는 클래스를 작성한다면 AutoCloseable을 반드시 구현해야한다.
public static String firstLineOfFile(String path) throw 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); } }
이때, 실전에서는 프로그래머에게 보여줄 예외 하나만 보존되고 여러 개의 다른 예외가 숨겨질 수도 있다.
이렇게 숨겨진 예외들은 스택 추적 내역에 숨겨졌다(suppressed)는 꼬리표를 달고 출력된다.
또한, 자바 7에서 Throwable 에 추가된 getSuppressed 메서드를 이용하면 프로그램 코드에서 가져올 수 있다.
+ try-with-resources에서도 catch 절을 쓸 수 있다.public static String firstLineOfFile(String path) throw IOException { try (BufferedReader br = new BufferedReader(new FileReader(path))) { return br.readLine(); } catch (Exception e) { return defaultVal; } }
결론
꼭 회수해야 하는 자원을 다룰 때 try-finally 말고, try-with-resources를 사용하자.
예외는 없다. 코드는 더 짧고 분명해지고, 만들어지는 예외 정보도 훨씬 유용하다.
또한 정확하고 쉽게 자원을 회수할 수 있다.