티스토리 뷰
자바 라이브러리에는 close 메서드를 호출하여 직접 닫아줘야 하는 자원이 많습니다.
InputStream, OutputStream, java.sql.Connection 등이 예로 있습니다.
자원 닫기는 클라이언트가 놓치기 쉬워서 예측할 수 없는 성능 문제로 이어지기도 합니다.
이런 자원 중 상당수가 안전망으로 finalizer를 사용하고 있지만 finalizer는 그리 믿을만하지 못합니다.(아이템 8)
전통적으로 자원이 제대로 닫힘을 보장하는 수단으로 try-finally가 쓰였습니다.
예외가 발생하거나 메서드에서 반환되는 경우를 포함해서 말입니다.
static String firstLineOfFile(String path) throws IOException {
BufferReader br = new BufferReader(new FileReader(path));
try {
return br.readLine();
} finally {
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 블록 모두에서 발생할 수 있는데, 만약 기기에 물리적인 문제가 생긴다면 firstLineOfFile 메서드 안의 readLine 메서드가 예외를 던지고, 같은 이유로 close 메서드도 실패할 것입니다.
이런 상황이라면 두 번째 예외가 첫 번째 예외를 완전히 집어삼켜 버립니다.
그러면 스택 추적 내역에 첫 번째 예외에 관한 정보는 남지 않게 되어, 실제 시스템에서의 디버깅을 몹시 어렵게 합니다.
물론 두 번째 예외 대신 첫 번째 예외를 기록하도록 코드를 수정할 수는 있지만, 코드가 너무 지저분해져서 실제로 그렇게까지 하는 경우는 거의 없습니다.
이러한 문제들은 자바 7에 생긴 try-with-resources 덕에 모두 해결되었습니다.
이 구조를 사용하려면 해당 자원이 AutoCloseable 인터페이스를 구현해야 합니다.
단순히 void를 반환하는 close 메서드 하나만 덩그러니 정의한 인터페이스입니다.
자바 라이브러리와 서드파티 라이브러리들의 수많은 클래스와 인터페이스가 이미 AutoCloseable 인터페이스를 구현하거나 확장해뒀습니다.
여러분도 닫아야 하는 자원을 뜻하는 클래스를 작성한다면 AutoCloseable을 반드시 구현하기 바랍니다.
다음은 try-with-resources를 사용해 위 코드를 재작성한 예시 코드들입니다.
static String firstLineOfFile(String path) throws IOException {
try (BufferReader br = new BufferReader(
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 버전이 짧고 읽기 수월할 뿐 아니라 문제를 진단하기도 훨씬 좋습니다.
firstLineOfFIle 메서드를 생각해봅시다.
readLine과 (코드에는 나타나지 않는) close 호출 양쪽에서 예외가 발생하면, close에서 발생한 예외는 숨겨지고 readLine에서 발생한 예외가 기록됩니다.
이처럼 실전에서는 프로그래머에게 보여줄 예외 하나만 보존되고 여러 개의 다른 예외가 숨겨질 수도 있습니다.
이렇게 숨겨진 예외들도 그냥 버려지지는 않고, 스택 추적 내역에 '숨겨졌다(suppressed)'는 꼬리표를 달고 출력됩니다.
또한, 자바 7에서 Throwable에 추가된 getSuppressed 메서드를 이용하면 프로그램 코드에서 가져올 수도 있습니다.
보통의 try-finally에서처럼 try-with-resources에서도 catch 절을 쓸 수 있습니다.
catch 절 덕분에 try 문을 더 중첩하지 않고도 다수의 예외를 처리할 수 있습니다.
다음 코드에서는 firstLineOfFile 메서드를 살짝 수정하여 파일을 열거나 데이터를 읽지 못했을 때 예외를 던지는 대신 기본값을 반환하도록 해봤습니다.
static String firstLineOfFile(String path) throws IOException {
try (BufferReader br = new BufferReader(
new FileReader(path))) {
return br.readLine();
} catch (IOException e) {
return defaultVal;
}
}
정리
- 꼭 회수해야 하는 자원을 다룰 때에는 try-finally 말고, try-with-resources를 사용하자.
- 예외는 없다.
- 코드는 더 짧고 분명해지고, 만들어지는 예외 정보도 훨씬 유용하다.
- try-finally로 작성하면 실용적이지 못할 만큼 코드가 지저분해지는 경우라도, try-with-resources로는 정확하고 쉽게 자원을 회수할 수 있다.
'JAVA > 이펙티브 자바' 카테고리의 다른 글
아이템[11]. equals를 재정의하려거든 hashCode도 재정의하라 (0) | 2022.03.27 |
---|---|
아이템[10]. equals는 일반 규역을 지켜 재정의하라 (0) | 2022.03.27 |
아이템[8]. finalizer와 cleaner 사용을 피하라 (0) | 2022.03.24 |
아이템[7]. 다 쓴 객체 참조를 해제하라 (0) | 2022.03.23 |
아이템[6]. 불필요한 객체 생성을 피하라 (0) | 2022.03.21 |
- Total
- Today
- Yesterday
- BAEKJOON
- AWS
- Effective Java
- programmers
- 백준
- 프로그래머스
- 알고리즘
- JPA
- 테라폼
- Olympiad
- 디자인 패턴
- 클린 아키텍처
- 정규표현식
- BOJ
- C++
- 디자인패턴
- 클린 코드
- Spring
- Algorithm
- MSA
- 이펙티브 자바
- 객체지향
- 코테
- kkoon9
- node.js
- 이팩티브 자바
- Java
- Spring Boot
- Kotlin
- kotest
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |