Skip to content

item 9 junghyunlyoo

JungHyunLyoo edited this page Jul 3, 2020 · 1 revision

자원 닫기의 중요성

InputStream, OutputStream, java.sql.Connection 등은 close 메소드를 호출해서 직접 닫아줘야 한다.

그런데 깜빡하고 자원을 닫지 않으면 예측할 수 없는 성능 문제로 이어질 수 있다.

이 상황을 방지하기 위해 finalizer를 활용할 수도 있지만 좋은 방법이 아니다. (item 8)

일반적으로 자원을 닫는 두가지 방법이 있다.

전통적인 방법인 try-fianlly와 java 7 이후 사용할 수 있는 try-with-resources이다.

자원을 닫는 전통적인 방법 :: try-finally

static String firstLineOfFile(String path) throws IOException {
    BufferedReader br = new BufferedReader(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의 2가지 단점이 있다.

첫 번째 단점은 가독성이 안좋다는 것이다.

두 번째 단점은 스택 추적이 어려울 수 있다는 것이다.

위의 firstLineOfFile 메소드를 통해 좀 더 자세히 살펴보자.

예외는 try 블록과 finally 블록 모두에서 발생할 수 있다.

예를 들어 기기에 물리적인 문제가 생긴다면 firstLineOfFile 메서드 내의

readLine 메서드가 예외를 던지고, 같은 이유로 close 메서드도 실패한다.

예외가 두개가 생겨버리고 close 예외가 readLine 예외를 덮어버린다.

실제로 스택 추적 내역에 첫 번째 예외에 관한 내용은 등장하지 않게 되고,

이는 실제 시스템에서의 디버깅을 몹시 어렵게 한다.

왜냐하면 첫 번째 예외부터 해결하는 것이 보통 에러 해결의 순서이기 때문이다.

두 번째 예외 대신 첫 번째 예외를 기록하도록 코드를 수정할 수도 있지만 코드가 너무 지저분해진다.

java 7에 등장한 try-with-resources

try-finally에서의 문제들을 try-with-resources가 깔끔하게 해결한다.

try-with-resources를 사용하려면 해당 자원이 AutoCloseable 인터페이스를 구현해야 한다.

왜냐하면 close 메소드를 무조건 호출하기 위해 AutoCloseable를 사용하기 때문이다.

AutoCloseable 인터페이스는 close 메서드 하나만 정의되어 있다.

자바 라이브러리와 서드파티 라이브러리들의 수많은 클래스와 인터페이스가 이미 AutoCloseable을 구현하거나 확장해뒀다.

닫아야 하는 클래스를 작성한다면 AutoCloseable을 반드시 구현하여 try-with-resources를 사용하자.

static String firstLineOfFile(String path) throws IOException {
    try (BufferedReader br = new BufferedReader(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);
    }
}

2개 이상의 자원을 사용하는 경우에도 굉장히 깔끔하다.


또한 try-finally보다 문제를 진단하기도 훨씬 편하다.

firstLineOfFile 메소드를 보자.

readLine과 코드에는 보이지 않는 close 모두에서 예외가 발생한다면

스택 추적 내역에 readLine에서 발생한 예외가 먼저 표시된다.

그리고 close에서 발생한 예외는 숨겨졌다는 꼬리표인 suppressed를 달고 그 이후에 같이 출력된다.

try-finally와 달리 첫 번째 예외부터 확인할 수 있는 것이다.

또한 아래처럼 java 7에서 Throwble에 추가된 getSuppressed 메서드를 이용하면 프로그램 코드에서 가져올 수도 있다.

try {
    testException1();
} catch (Throwable e) {
    Throwable[] suppExe = e.getSuppressed();

    for (int i = 0; i < suppExe.length; i++) {
        System.out.println("Suppressed Exceptions:");
        System.out.println(suppExe[i]);
    }
}

try-with-resources에서 catch 사용하기

try-finally처럼 try-with-resources에서도 catch 절을 쓸 수 있다.

catch 절 덕분에 try 문을 더 중첩하지 않고도 다수의 예외를 처리할 수 있다.

static String firstLineOfFile (String path, String defaultVal){
    try (BufferedReader br = new BufferedReader(
            new FileReader(path))) {
        return br.readLine();
    } catch (IOException e) {
        return defaultVal;
    }
}
Clone this wiki locally