아이템 9. try-finally보다는 try-with-resources를 사용하라.

문법식·2022년 3월 9일
0

Effective Java 3/E

목록 보기
9/52

자바 라이브러리에서는 close()메서드를 호출해 직접 닫아줘야 하는 자원이 많다. 그러나 자원 닫기는 클라이언트가 놓치기 쉬워서 예측할 수 없는 성능 문제로 이어지기도 한다. 전통적으로 자원이 제대로 닫힘을 보장하는 수단으로 try-finally가 쓰였다. 예외가 발생하거나 메서드에서 반환되는 경우를 포함해서 말이다.

try-finally

예시 코드

public class FirstError extends RuntimeException{
}
public class SecondError extends RuntimeException{
}
public class MyResource implements AutoCloseable{
    public void doSomething(){
        System.out.println("Do something");
        throw new FirstError();
    }

    @Override
    public void close() throws Exception {
        System.out.println("Close My Resource");
        throw new SecondError();

    }
}
public class AppRunner {
    public static void main(String[] args) throws Exception {
        /**
         * 고전적인 예외 처리 방법
         */
        MyResource myResource=new MyResource();
        try {
            myResource.doSomething();
        }finally {
            myResource.close();
        }
}

try-finally를 제대로 사용한 위의 코드 예제조차 미묘한 결점이 있다. 예외는 try블록과 finally블록 모두에서 발생할 수 있다. 위 코드 try블록에서 myResource.doSomething()을 호출하면 FirstError가 발생한다. 그러면 finally블록으로 간다. 그러나 finally블록에서 myResource.close()를 실행하면 SecondError가 발생한다. 이런 상황이면 아래의 출력 결과로 알 수 있듯이 두 번째 예외가 첫 번째 예외를 집어삼켜 버린다.

출력 결과

Do something
Close My Resource
Exception in thread "main" Item09.SecondError
	at Item09.MyResource.close(MyResource.java:12)
	at Item09.AppRunner.main(AppRunner.java:21)

그러면 스택 추적 내역에 첫 번째 예외에 관한 정보가 남지 않게 되어, 실제 시스템에서의 디버깅을 몹시 어렵게 한다. 왜나하면 당연히 문제 발생을 진단하려면 처음 발생한 예외를 보고 싶기 떄문이다.

또한, 자원을 하나 더 사용하게 되면 코드가 많이 지저분해진다.

예시 코드

public class AppRunner {
    public static void main(String[] args) throws Exception {
        /**
         * 고전적인 예외 처리 방법
         */
        MyResource myResource=new MyResource();
        try {
            myResource.doSomething();
            MyResource newMyResource = null;
            try{
                newMyResource=new MyResource();
                newMyResource.doSomething();
            }finally {
                if(newMyResource!=null) {
                    newMyResource.close();
                }
            }
        }finally {
            myResource.close();
        }
    }
}

try-with-resources

이러한 문제들은 자바 7에서 추가된 try-with-resources로 모두 해결할 수 있다. 이 구조를 사용하려면 해당 자원이 AutoCloseable 인터페이스를 구현해야 한다. 단순히 void를 반환하는 close()메서드를 하나만 덩그러니 정의한 인터페이스다. 닫아야하는 자원을 뜻하는 클래스를 작성한다면 AutoCloseable을 반드시 구현하길 바란다.

예시 코드

public class FirstError extends RuntimeException{
}
public class SecondError extends RuntimeException{
}
public class MyResource implements AutoCloseable{
    public void doSomething(){
        System.out.println("Do something");
        throw new FirstError();
    }

    @Override
    public void close() throws Exception {
        System.out.println("Close My Resource");
        throw new SecondError();

    }
}
public class AppRunner {
    public static void main(String[] args) throws Exception {
        /**
         * try-with-resource
         */
        try(MyResource myResource=new MyResource()) {
            myResource.doSomething();
        }
}

try-with-resources가 위에서의 try-finally를 사용한 것보다 코드가 훨씬 간결하고 일기 수월할 뿐만 아니라 문제를 진단하기도 훨씬 좋다.

출력 결과

Do something
Close My Resource
Exception in thread "main" Item09.FirstError
	at Item09.MyResource.doSomething(MyResource.java:6)
	at Item09.AppRunner.main(AppRunner.java:28)
	Suppressed: Item09.SecondError
		at Item09.MyResource.close(MyResource.java:12)
		at Item09.AppRunner.main(AppRunner.java:27)

위의 try-finally 코드에서 발생한 예외 상황과 똑같은 예외 상황이다. 즉,myResource.doSomething()에서 FirstErrormyResource.close()에서 SecondError가 발생한 상황이다. 이 경우 출력 결과를 보면 close()에서 발생한 예외는 숨겨지고 doSomething에서 발생한 첫 번째 예외가 기록된 것을 알 수 있다. SecondError의 경우 숨겨지는데 숨겨진다고 버려지는게 아니라, 스택 추적 내역에 숨겨졌다(suppressed)라는 꼬리표를 달고 출력된다. 또한 자원을 여러 개 쓰더라도 코드가 지저분해지지 않는다.

예시 코드

public class AppRunner {
    public static void main(String[] args) throws Exception {
        /**
         * try-with-resource
         */
        try(MyResource myResource1=new MyResource();
            MyResource myResource2=new MyResource()) {
            myResource1.doSomething();
            myResource2.doSomething();
        }
    }
}

보통의 try-finally에서처럼 try-with-resources에서도 catch절을 쓸 수 있다. catch절 덕분에 try문을 더 중첩하지 않고도 다수의 예외를 처리할 수 있다.

profile
백엔드

0개의 댓글