2022. 6. 3. 14:23ㆍJAVA/Effective Java
1. try-finally 방식의 용도
자바 라이브러리에는 InputStream, OutputStream, java.sql.Connection 등과 같은 입출력 클래스는 close 메서드를 호출해 직접 닫아줘야 하는 자원이 많습니다. 전통적으로 자원이 제대로 닫힘을 보장하는 수단으로 try-finally 방식을 사용하여 인스턴스가 실행 도중 예외가 발생하거나 메서드에서 반환되는 경우를 포함하여 자원을 안정적으로 회수할 수 있도록 합니다.
다음 예제는 try-finally 방식을 사용하여 일반적으로 자원을 회수하는 방식입니다.
// try-finally 구문을 활용한 일반적인 자원 회수
static String firstLineOfFile(String path) throws IOException {
// 회수해야할 자원
BufferedReader br = new BufferedReader(new InputStreamReader(TryFinallyTest.class.getResourceAsStream(path)));
try {
return br.readLine();
}finally {
br.close();
}
}
2. try-finally보다 try-with-resources 방식을 사용해야 하는 이유
- try-finally 방식에서 try 블럭과 finally 블럭에서 둘다 예외가 발생하면 try 블럭에서 발생한 예외는 무시되고 finally 블럭에서 발생한 예외만 출력됨
- close해야 할 자원이 둘 이상이라면 try-finally 구문이 복잡해진다.
다음 예제는 try-finally 방식으로 try블럭과 finally 블럭에서 둘다 예외를 발생시킬 때 예외 클래스가 어떻게 나오는지 확인하는 예제입니다.
// try 블럭과 finally 블럭에서 예외가 발생시 try 블럭의 예외는 무시된다.
static String throwDoubleMethod() throws IOException {
try {
throw new IOException("try 블럭에서 예외발생");
}finally {
throw new IOException("finally 블럭에서 예외발생");
}
}
@Test
void doubleThrowMethodTest() {
String result = null;
try {
throwDoubleMethod();
} catch (IOException e) {
result = e.getMessage();
e.printStackTrace();
}
assertEquals("finally 블럭에서 예외발생", result);
}
java.io.IOException: finally 블럭에서 예외발생
at role9.TryFinallyTest.throwDoubleMethod(TryFinallyTest.java:90)
at role9.TryFinallyTest.doubleThrowMethodTest(TryFinallyTest.java:45)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
...
위 실행결과와 같이 try 블럭과 finally 블럭에서 예외가 둘다 발생할 경우 try 블럭의 예외는 무시되고 finally 블럭의 예외만 출력되는 것을 볼 수 있습니다. 이렇게 되면 스택 추적 내역에서 첫번째 예외에 관한 정보는 남지 않게 되어, 실제 시스템에서의 디버깅을 어렵게 만듭니다.
다음 예제는 try-finally 방식에서 close 해야할 자원이 둘 이상인 경우 구분이 어떻게 되는지 확인하는 예제입니다.
// 자원이 둘 이상이면 try-finally 방식은 너무 지저분해진다
static void copy(String src, String dst) throws IOException {
InputStream in = TryFinallyTest.class.getResourceAsStream(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 구문이 중첩되기 때문에 매우 복잡해집니다.
3. try-with-resources 방식
try-with-resources 방식은 try에 자원 객체를 전달하면 try 코드 블록이 종료되면 자동으로 자원을 close하는 방식입니다. 위 예제에서 firstLineOfFile 메서드와 copy 메서드를 try-with-resources 방식으로 개선하면 다음과 같습니다.
// try-with-resources : 자원을 회수하는 최선책
static String firstLineOfFile(String fileName) throws IOException {
try(BufferedReader br = new BufferedReader(new InputStreamReader(
TryWithResourceTest.class.getResourceAsStream(fileName)
))
){
return br.readLine();
}
}
// 복수의 자원을 처리하는 try-with-resources
static void copy(String src, String dst) throws IOException {
try(InputStream in = TryWithResourceTest.class.getResourceAsStream(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 블럭이 종료되면 자원 객체 내부에 구현된 close 메서드를 실행합니다.
try-with-resources 방식의 장점
- 다중 예외가 발생한 경우 무시되지 않고 기록됨
- 예를 들어 readLine과 close 호출 양쪽에서 예외가 발생하면 close에서 발생한 예외는 숨겨지고 readLine에서 발생한 예외가 기록됩니다. close에서 발생한 예외는 suppressed라는 꼬리표를 달고 출력됩니다.
- finally문을 사용하지 않기 때문에 복잡해지지 않아 가독성이 좋아짐
4. try-with-resources 방식을 사용하기 위한 조건
- try-with-resources 구문을 사용하려면 해당 자원이 AutoCloseable 인터페이스를 구현해야 합니다.
- 자바 라이브러리와 서드파티 라이브러리들의 수많은 클래스와 인터페이스가 이미 AutoCloseable을 구현하거나 확장하였습니다.
다음 예제는 어떤 한 클래스가 AutoCloseable 인터페이스를 구현하여 try-with-resources 방식을 사용하여 자원을 회수하는 예제입니다.
public class CloseableResource implements AutoCloseable {
private BufferedReader reader;
public CloseableResource(String fileName) {
InputStream input = CloseableResource.class.getResourceAsStream(fileName);
reader = new BufferedReader(new InputStreamReader(input));
}
public String readFirstLine() throws IOException {
String firstLine = reader.readLine();
return firstLine;
}
@Override
public void close() {
try {
reader.close();
System.out.println("Closed BufferedReader in the close method");
} catch (IOException e) {
// handle exception
}
}
}
@Test
void tryWithResourceTest() {
String line = null;
try(CloseableResource cr = new CloseableResource("file.txt")){
line = cr.readFirstLine();
} catch (IOException e) {
e.printStackTrace();
}
assertEquals("hello world", line);
}
Closed BufferedReader in the close method
위 실행결과를 통해서 try 블럭을 마치면 CloseableResource 인스턴스는 close 메서드를 수행하여 BufferedReader br 참조변수를 close합니다.
위와 같이 닫아야 하는 자원을 뜻하는 클래스를 작성한다면 AutoCloseable 인터페이스를 반드시 구현해야합니다.
정리하며
- 반드시 회수해야 하는 자원을 다룰 때는 try-finally 말고, try-with-resources를 사용하자
References
source code : https://github.com/yonghwankim-dev/effective_java/tree/master/src/role9
effective java 3/E
'JAVA > Effective Java' 카테고리의 다른 글
[Java][Effective Java] item 10. equals는 일반 규약을 지켜 재정의하라 (일관성) (0) | 2022.06.07 |
---|---|
[Java][Effective Java] item 10. equals는 일반 규약을 지켜 재정의하라 (대칭성, 추이성) (0) | 2022.06.07 |
[Java][Effective Java] item 7. 다 쓴 객체 참조를 해제하라 (0) | 2022.05.24 |
[Java][Effective Java] item 6. 불필요한 객체 생성을 피하라 (0) | 2022.05.18 |
[Java][Effective Java] item 5, 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라 (0) | 2022.05.17 |