본문 바로가기
Effective Java

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

by byeongoo 2021. 1. 24.

■ 자원 닫기

자바 라이브러리에서는 close 메서드를 호출해 직접 닫아줘야 하는 자원이 많다. InputStream, OutputStream, java.sql.Connection 등이 좋은 예이다. 클라이언트가 놓치기 쉬워서 예측할 수 없는 성능 문제로 이루어지기도 한다. 이런 자원 중 상당수가 안전망으로 finallizer를 활용하지만 그리 믿을만하지 못하다.

 

■ try-finally를 이용한 자원 닫기

아래 코드는 닫을 자원이 1개일 때 try-finally를 사용해서 닫는 예시 코드 이다.

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

만약 닫을 자원이 2개라면 아래와 같이 닫아준다.

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

위와 같이 자원을 닫을 경우 2가지의 문제가 있다.

 

  • 가독성이 안좋다
  • 예외는 try블록과 finally 블록 모두에서 발생할 수 있다. try문과 finally문 모두에서 Exception이 발생하면 finally에서 발생한 Exception만 보이기 때문에 디버깅이 어렵다.

■ java 7에 등장한 try-with-resources

try-with-resources를 이용하면 try-finally의 문제들을 깔끔하게 해결할 수 있다. 우선 try-with-resources를 사용하려면 해당 자원이 AutoCloseable 인터페이스를 구현 해야한다. 왜나하면 close 메서드를 무조건 호출하기 위해서는 AutoCloseable를 사용하기 때문이다. 이미 자바 라이브러리와 서드 파티 라이브러리들의 수많은 클래스와 인터페이스가 이미 AutoCloseable을 구현하거나 확자해뒀다.

 

한 개의 자원을 닫는 코드만 먼저 보면 상당히 깔끔하다. 또한 readLine 메서드와 코드에는 보이지 않는 close 모두에서 예외가 발생한다면 스택 추적 내역에 readLine에서 발생한 예외가 먼저 표시된다. 그리고 close에서는 발생한 예외가 숨겨졌다는 꼬리표인 suppressed를 달고 그 이후에 같이 출력 된다. try-fianlly와 달리 첫번째 예외 부터 확인이 가능하다.

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

BufferedReader 클래스의 경우 아래와 같이 close 메서드를 구현하고 있는 것을 볼 수 있다.

public void close() throws IOException {
    synchronized(this.lock) {
        if (this.in != null) {
            try {
                this.in.close();
            } finally {
                this.in = null;
                this.cb = null;
            }

        }
    }
}

2개 이상의 자원을 사용하는 경우도 코드가 깔끔하다.

 

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

 

■ try-with-resources에서 catch 사용

try-with-resources에서도 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;
    }
}

 

REFERENCE