(Effective Java 3/E) 109. Item 9 - Prefer try-with-resources to try-finally

Prefer try-with-resources to try-finally

  • 2판 제목 : 없음
  • 3판 제목 : try-finally 보다는 try-with-resources를 사용하라

자바에는 InputStream, OutputStream, java.sql.Connection 등 사용 후 직접 닫아주어 자원을 반환하는 객체들이 있다.

자원의 반환은 누락하기가 쉬운 관계로 종료자를 활용할 수 있지만 여러가지 단점이 있기에 쉽게 사용하기 어려운 문제가 있다.

전통적으로는 아래와 같이 try-finllay를 이용해 무조건 호출되는 finally 블록에서 자원을 반환하였다.

1
2
3
4
5
6
7
8
static String firstLineOfFile(String path) throws IOException {
BufferedReader br = new BufferedReader(new FileReader(path));
try {
return br.readLine();
} finally {
br.close();
}
}

허나 반환해야하는 자원이 둘 이상이면 코드가 배우 난잡해진다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 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();
}
}

자바 7부터는 try-with-resources를 활용해 이 복잡한 구조를 해소할 수 있게 되었다.

try-with-resources를 사용하려면, 이를 이용하는 자원이 AutoCloseable 인터페이스를 구현해야하는데,

이미 상당수의 자원들이 해당 인터페이스를 구현하고 있다.

1
2
3
public interface AutoCloseable {
void close() throws Exception;
}

실제로 적용한다면 아래와 같은 코드를 얻게 된다.

1
2
3
4
5
6
7
8
9
 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);
}
}

좀 더 가독성이 향상되었음을 느낄 수 있다.

장점은 가독성뿐만이 아니다.

아래와 같은 예제를 보자.

1
2
3
4
5
static String firstLineOfFile(String path) throws IOException {
try(BufferedReader br = new BufferedReader(new FileReader(path)) {
return br.readLine();
}
}

위의 예제에서 readline과 내부적으로 호출될 close 함수 양쪽에서 에외가 발생한다면 어떻게 될까?

만약 둘 다 예외가 발생한다면, close에서 발생한 예외는 숨겨지고 readLine에서 발생하 예외만 출력된다.

즉 개발자가 직접 손을 대야하는 영역에 한해서만 예외를 출력해주는 것이다.

물론 숨겨진 예외들도 StackTrace 내에 suppressed 표식을 달고 출력은 된다.

또한 보통의 try-finally 처럼 catch 블록을 추가하는 것도 가능하다.

위의 예제를 살짝 수정해보자.

1
2
3
4
5
6
7
static String firstLineOfFile(String path, String defaultValue) throws IOException {
try (BufferedReader br = new BufferedReader(new FileReader(path)) {
return br.readLine();
} catch(IOException e) {
return defaultValue;
}
}