자바 라이브러리는 close
메서드를 통해 호출해 닫아줘야 하는 자원이 많다.
close
메서드가 호출되지 않으면 이는 성능 문제로 이어질 수 있다. 전통적으로 close
메서드는 try-finally
의 finally 절에서 직접 호출해줬는데 이는 다양한 문제가 발생할 수 있다.
static String firstLineOfFile(String path) throws IOException {
BufferedReader br = new BufferedReader(new FileReader(path));
try {
return br.readLine();
} finally {
br.close();
}
}
자원을 하나만 사용하는 경우 코드 자체는 별로 복잡해 보이지 않는다. 그런나 이 경우에도 미묘한 문제가 발생할 수 있다.
파일을 읽어들이는 기기 자체에 문제가 생긴다면 br.readLine()
이 실패하여 예외가 발생할 것이고 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의 문제점들은 자바7에서 등장한 try-with-resources 문을 사용하면 깔끔하게 해결된다.
이 문법을 사용하려면 사용하는 자원이 Autoclosable
인터페이스를 구현해야 한다. 이 인터페이스는 반환값이 없는 close
메서드 하나만 정의한 매우 간단한 인터페이스다. 이미 많은 자바 라이브러리, 서드파티 라이브러리들이 이 인터페이스를 구현했다.
만약 닫아야 하는 자원을 사용하는 클래스를 작성한다면 꼭 Autoclosable
을 구현하도록 하자.
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);
}
}
간단하고 의도도 명확하다. close()
는 보이지 않지만 자동으로 호출된다.
예외가 삼켜지는 문제가 해결된다. 기기에 문제가 발생하여 파일을 읽어들이는 부분(readLine/read)과 파일을 종료하는 부분(close)에서 동시에 예외가 발생해도 먼저 발생한 예외가 기록된다. 그 외의 예외들은 버려지지 않고 스택 추적내에 숨겨졌다는 suppressed
꼬리표를 달고 출력 된다. 필요하다면, 자바7에서 추가된 Throwable
의 getSuppressed
메서드를 통해 프로그램 코드로 가져올 수도 있다.
닫아줘야 하는 자원이 있다면 try-finally 대신 try-with-resources를 사용하도록 하자. 다음과 같은 장점이 있다.
코드가 더 짧아지면서 의도가 명확해진다.
만들어지는 예외 정보가 훨씬 유용하다.
실수로 자원을 회수하지 않아 발생할 수 있는 성능문제가 발생하지 않는다.