자바 라이브러리에는 close 메서드를 호출해 직접 닫아줘야 하는 자원이 많다. InputStream, OutputStream, Connection 등이 좋은 예이다. 자원 닫기는 클라이언트가 놓치기 쉬워서 예측할 수 없는 성능 문제로 이어진다.
finalizer로 안전망을 구축하지만, 믿을만하지 못하다.
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-with-resources 덕분에 해결되었다. 이 구조를 사용하려면 해당 자원이 AutoCloseable 인터페이스를 구현해야 한다. 단순히 void를 반환하는 close 메서드 하나 뿐이다.
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 방식이 더 짧고 읽기 수월할 뿐 아니라 문제를 진단하기도 훨씬 좋다. 코드에 나타나지 않는 readLine과 close 호출 양쪽에서 예외가 발생하면, close에서 발생한 예외는 숨겨지고 readLine에서 발생한 예외가 기록된다. 이처럼 실전에서 프로그래머에게 보여줄 예외 하나만 보존되고 여러 개의 다른 예외가 숨겨질 수도 있다. 이렇게 숨겨진 예외들도 그냥 버려지지 않고, 스택 추적 내역에 surpressed
키워드를 달고 출력된다. 또한 자바 7에서 Throwabl에 추가된 getSurppressed 메서드를 이용하면 프로그램 코드에서 가져올 수도 있다.
보통의 try-finally에서처럼 try-with-resources에서도 catch 절을 쓸 수 있다. catch 절 덕분에 try 문을 더 중첩하지 않고도 다수의 예외를 처리할 수 있다.
static String firstLineOfFile(String path, String defaultVal) throws IOException {
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로는 정확하고 쉽게 자원을 회수할 수 있다.