자바 라이브러리에는 InputStream
, OutputStream
, java.sql.Connection
등과 같이 정리(close()
) 가능한 리소스가 많은데 그런 리소스를 사용하는 클라이언트코드가 리소스 정리를 잘 안하거나 잘못하는 경우가 있다.
이런 리소스정리는 클라이언트가 놓치기 쉬워 성능문제로 이어지곤한다. 대다수의 이런 리소스들이 안전망으로 finalize 를 활용하고 있지만 그리 좋은 방법이라고 할 수는 없다. 전통적으로 리소스를 닫는 수단으로 try-finally 가 사용되어왔다.
[리소스 정리를 못하는 경우]
public class FirstError extends RuntimeException{
}
public class SecondException extends RuntimeException{
}
public class MyResource implements AutoCloseable{
public void doSomething() throws FirstError{
System.out.println("doing Something");
throw new FirstError();
}
@Override
public void close() throws SecondException {
System.out.println("clean my resource");
throw new SecondException();
}
}
public class MyRunner {
public static void main(String[] args){
final MyResource myResource = new MyResource();
myResource.doSomething(); // Exception이 발생해 close 메서드에 도달하지 못하여 자원정리를 하지 못한다.
myResource.close();
}
}
[try-finally : 더이상 자원을 회수하는 최선의 방법이 아니다.]
public class MySecondRunner {
public static void main(String[] args) {
MyResource myResource = null;
try {
myResource = new MyResource();
myResource.doSomething();
}finally {
if (myResource != null) {
myResource.close();
}
}
}
}
위 코드는 하나의 자원만을 사용한 예시이다. 만약 여기서 자원을 하나 더 사용한다면?
[자원이 둘 이상이면 try-finally 방식은 지저분하다.]
public class MyNestedRunner {
public static void main(String[] args) {
MyResource myResource = null;
try {
myResource = new MyResource();
myResource.doSomething();
MyResource secondResource = null;
try {
secondResource = new MyResource();
secondResource.doSomething();
} finally {
if (secondResource != null) {
secondResource.close();
}
}
} finally {
if (myResource != null) {
myResource.close();
}
}
}
}
중첩 try-finally
문에서 예외가 발생하면 SecondException
이 출력되고 먼저 발생한 FirstException
은 두번째 예외에 덮히면서 확인이 불가능해진다.
이로인해 디버깅이 힘들어진다. 또한 위 코드는 코드가 장황해지면서 실수를 해 오류가 발생할 가능성을 높인다.
이러한 문제점을 자바 7에서 발생한 try-with-resources
덕에 해결할 수 있다. try-with-resources
는 가독성도 좋고, 문제를 분석할때도 좋다. 왜냐하면 처음 발생한 예외가 두번째 발생한 예외로 인해 덮히지 않기 때문이다.
이 구조를 사용하려면 해당 자원이 AutoCloseable
인터페이스를 구현해야한다. (AutoCloseable
인터페이스는 단순히 void
를 반환하는 close()
만을 정의한 인터페이스이다.)
만약, 회수해야 하는 자원을 뜻하는 클래스를 작성한다면 반드시 AutoCloseable
구현하는것이 좋다.
[try-with-resources - 자원을 회수하는 최선책]
public class MyAutoCloseableRunner {
public static void main(String[] args) {
try (MyResource myResource = new MyResource()) {
myResource.doSomething();
}
}
}
/* 실행결과
doing Something
clean my resource
Exception in thread "main" item9.FirstError
at item9.MyResource.doSomething(MyResource.java:6)
at item9.MyAutoCloseableRunner.main(MyAutoCloseableRunner.java:6)
Suppressed: item9.SecondException
at item9.MyResource.close(MyResource.java:12)
at item9.MyAutoCloseableRunner.main(MyAutoCloseableRunner.java:5)
*/
close
를 명시적으로 사용하지 않아도 AutoCloseable
을 사용하면 자동으로 호출해준다.
또한 에러는 첫번째 발생한 에러 뒤에 쌓아(suppressed)둔다. 처음 발생한 에러를 더 중요시 여긴다. 그리고 Throwable
의 getSuppressed
메서드를 사용해 쌓여있는 에러를 사용할 수 있다.
[복수의 자원을 처리하는 try-with-resources]
public class MyAutoCloseableRunner {
public static void main(String[] args) {
try (MyResource myResource = new MyResource();
MyResource mySecondResource = new MyResource()) {
myResource.doSomething();
mySecondResource.doSomething();
}
}
}
/* 실행결과
doing Something
clean my resource
clean my resource
item9.FirstError
at item9.MyResource.doSomething(MyResource.java:6)
at item9.MyAutoCloseableRunner.main(MyAutoCloseableRunner.java:9)
Suppressed: item9.SecondException
at item9.MyResource.close(MyResource.java:12)
at item9.MyAutoCloseableRunner.main(MyAutoCloseableRunner.java:7)
Suppressed: item9.SecondException
at item9.MyResource.close(MyResource.java:12)
at item9.MyAutoCloseableRunner.main(MyAutoCloseableRunner.java:7)
*/
try-with-resources 를 사용하는 것이 짧고 읽기 수월하며 문제진단에도 효율적이다. 물론, catch 절을 사용해서 다수의 예외를 처리하는 것도 가능하다.
[try-with-resources가 catch 절과 함께 쓰이는 경우]
public class MyAutoCloseableRunner {
public static void main(String[] args) {
try (MyResource myResource = new MyResource();
MyResource mySecondResource = new MyResource()) {
myResource.doSomething();
mySecondResource.doSomething();
}catch (Exception exception){
exception.printStackTrace();
}
}
}
꼭 회수해야 하는 자원을 다룰때는 try-with-resources 를 사용하는 것이 좋다.
[Reference]
춤추는 개발자
백기선 - 이펙티브자바