자바 라이브러리에는 close메서드를 호출해 직접 닫아줘야 하는 자원이 많다. InputStream, OutputStream,java.sql.Connection 등이 좋은 예다.
자원 닫기는 클라이언트가 놓치기 쉬워서 예측할 수 없는 성능문제로 이어지기도 한다.이러한 자원 중 상당수가 안전망으로 finalizer를 활용하고는 있지만 finalizer는 그리 믿음직하지 못 한다.
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class TopLine {
// 코드 9-1 try-finally - 더 이상 자원을 회수하는 최선의 방책이 아니다! (47쪽)
static String firstLineOfFile(String path) throws IOException {
BufferedReader br = new BufferedReader(new FileReader(path));
try {
return br.readLine();
} finally {
br.close();
}
}
public static void main(String[] args) throws IOException {
String path = args[0];
System.out.println(firstLineOfFile(path));
}
}
해당 하나의 자원에서는 나쁘지 않다.
// 코드 9-2 자원이 둘 이상이면 try-finally 방식은 너무 지저분하다! (47쪽)
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();
}
}
해당 코드에서는 두 번째 에러가 첫 번째 에러를 묻어버리는 경우가 발생한다. 그러면 문제를 디버깅해서 찾아내기도 어려워진다. 물론 두 번째 예외 대신 첫 번째 예외를 기록하도록 코드를 수정하면 되지만 너무 지저분해진다.
// 코드 9-3 try-with-resources - 자원을 회수하는 최선책! (48쪽)
static String firstLineOfFile(String path) throws IOException {
try (BufferedReader br = new BufferedReader(
new FileReader(path))) {
return br.readLine();
}
}
이 구조를 사용하려면 해당 자원이 AutoCloseable 인터페이스를 구현해야한다. 단순히 void를 반환하는 close메서드 하나만 덩그러니 정의한 인터페이스이다.
다수의 자원을 이용할 때
// 코드 9-4 복수의 자원을 처리하는 try-with-resources - 짧고 매혹적이다! (49쪽)
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)처음 발생한 에러를 중요시 여긴다. 그리고 Throwable
의 getSuppressed
메소드를 사용해서 뒤에 쌓여있는 에러를 코딩으로 사용가능하다.
catch 블록은 try-finally에서처럼 사용가능하다.
꼭 회수해야되는 자원이 있을 경우에는 try-with-resource를 사용하자. 코드는 더 짧고 분명해지고, 만들어지는 예외 정보도 훨씬 유용하다.