[Effective Java] 아이템 9 : try-finally 보다는 try-with-resources를 사용하라

Rupee·2022년 5월 27일
0

이펙티브 자바

목록 보기
9/76
post-thumbnail

자바 라이브러리에는 close() 메서드를 호출해 직접 닫아줘야 하는 자원이 많다. ex) InputStream, OutputStream, java.sql.Connection

이런 자원 중 상당수가 안전망으로 finalizer를 활용하고 있지만, 앞에서 봤듯이 자원이 제대로 닫힘을 보장할 수 없다.

따라서 이들 대신 자원이 제대로 닫힘을 보장하는 수단 두가지를 보고, try-with-resources 를 사용해야 하는 이유에 대해 알아보자.

☁️ try-finally

예외가 발생하거나, 메서드에서 반환되는 경우를 포함해 전통적으로는 try-finally 가 사용되었다.

public class TopLine {
    static String firstLineOfFile(String path) throws IOException {
        BufferedReader br = new BufferedReader(new FileReader(path));
        try {
            return br.readLine();
        } finally {
            br.close();
        }
    }

    public static void main(String[] args) throws IOException {
        String path = args[0];
        System.out.println(firstLineOfFile(path));
    }
}

다음은 자원을 여러개 사용한 방식이다. 자원이 둘 이상이 되면, 모두 try-finally로 자원을 닫아줘야 하니 코드가 너무 지저분해진다.

public class Copy {
    private static final int BUFFER_SIZE = 8 * 1024;

    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();
        }
    }

    public static void main(String[] args) throws IOException {
        String src = args[0];
        String dst = args[1];
        copy(src, dst);
    }
}

하지만 이보다도 더 치명적인 문제점이 존재한다.

바로 예외가 발생했을때 나중에 발생한 예외에 의해서 첫번째 예외가 먹혀져 실제 시스템에서의 디버깅이 힘들어진다는 것이다.

예를 들어, 기기에 물리적인 문제가 생긴다면 firstLineOfFile 안의readLine() 메서드와 close() 메서드 모두 예외를 던지게 될 것이다. 이런 상황에서 스택 추적 내역에 첫 번째 예외에 관한 정보는 남지 않게 된다.

☁️ try-with-resources

위에서 살펴본 문제들은 자바 7에서 처음 언급된 try-with-resources 로 인해 해결되었다.

해당 자원이 AutoCloseable 인터페이스를 구현했다면, try-with-resources 내부에서 사용할 수 있다. 참고로, 자바 라이브러리와 서드파티 라이브러리들의 수많은 클래스와 인터페이스는 이미 AutoCloseable를 구현하거나 확장해두었다.

아래는 두번째 예시를 재작성 한 코드이다.

public class Copy {
    private static final int BUFFER_SIZE = 8 * 1024;
    
    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);
        }
    }

    public static void main(String[] args) throws IOException {
        String src = args[0];
        String dst = args[1];
        copy(src, dst);
    }
}

try-with-resources 장점

  1. 바뀐 버전이 짧고 읽기 수월할 뿐만 아니라, 문제를 진단하기도 훨씬 좋다.
  2. 예외를 먹지 않으므로, 디버깅이 쉬워진다.

firstLineOfFile() 메서드에서 readLine()close() 양쪽에서 예외가 발생하면, 후자에서 발생한 예외는 숨겨지고 전자에서 발생한 예외가 기록이 된다.

더불어, 숨겨진 예외들도 버려지는 것이 아닌 스택 추적 내역에 숨겨졌다(suppressed) 꼬리표를 달고 출력된다. 자바 7에서 Throwable.getSuppressed() 메서드를 이용하면 프로그램 코드에서 가져올 수도 있다.

☁️ try-with-resources + catch 절

catch 절을 사용하면, try 문을 중첩하지 않고도 다수의 예외를 처리할 수 있다.

public class TopLineWithDefault {
    static String firstLineOfFile(String path, String defaultVal) {
        try (BufferedReader br = new BufferedReader(
                new FileReader(path))) {
            return br.readLine();
        } catch (IOException e) {
            return defaultVal;
        }
    }

    public static void main(String[] args) throws IOException {
        String path = args[0];
        System.out.println(firstLineOfFile(path, "Toppy McTopFace"));
    }
}
profile
개인용으로 공부하는 공간입니다. 잘못된 부분은 피드백 부탁드립니다!

0개의 댓글