[이펙티브 자바] 아이템9 | try-finally 보다는 try-with-resources를 사용하라.

제롬·2022년 1월 11일
0

이펙티브자바

목록 보기
9/25

리소스(자원)관리

자바 라이브러리에는 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

[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는 가독성도 좋고, 문제를 분석할때도 좋다. 왜냐하면 처음 발생한 예외가 두번째 발생한 예외로 인해 덮히지 않기 때문이다.

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)둔다. 처음 발생한 에러를 더 중요시 여긴다. 그리고 ThrowablegetSuppressed 메서드를 사용해 쌓여있는 에러를 사용할 수 있다.

[복수의 자원을 처리하는 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]
춤추는 개발자
백기선 - 이펙티브자바

0개의 댓글