본문 바로가기
책/effective java

[effective java] item 9. try-finally보다는 try-with-resources를 사용하라

by 2245 2023. 7. 30.

결론

닫아야 하는 자원을 다룰 때는 try-finally 말고, try-with-resources를 사용하자.
예외는 없다. 
코드는 더 짧아지고 분명해지며, 스택 추적 내역에 예외가 남겨지기 때문에 훨씬 유용하다. 
단, 자원이 AutoCloseable 인터페이스를 구현해야 한다. 

try-with-resources + catch 예제

import java.io.*;

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);
        } catch(IOException e) {
        	...
        }
    }

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

 

 

설명

상황 

자바 라이브러리에는 close 메서드를 호출하여 직접 닫아줘야 하는 자원이 많습니다.

ex) InputStream, OutputStream, java.sql.Connection 등

 

자원 닫기는 클라이언트가 놓치면 예측할 수 없는 성능 문제로 이어집니다. 

이런 자원 중 상당수가 안전망으로 finalizer를 활용하고는 있지만, finalizer는 그리 믿음직하지 못합니다. 

 

 

try-finally - 기존 방식 

try 내부에 예외가 발생하거나, 자원이 반횐되어도 finally 안의 코드가 실행됩니다. 

public class TopLine {
    // 코드 9-1 try-finally - 더 이상 자원을 회수하는 최선의 방책이 아니다! (47쪽)
    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 블록 모두에서 발생할 수 있습니다. 
    • 만약, 물리적인 문제가 생긴다면 readLine() 메서드가 예외를 던지고, 같은 이유로 close() 메서드도 실패할 것입니다.
    • 이런 경우, 스택 추적 내역에서 첫 번째 예외에 관한 정보는 남지 않습니다. 
    • 따라서, 실제 시스템에서 디버깅을 매우 어렵게 합니다. (일반적으로 문제를 진단할 때 처음 발생한 예외를 봅니다.)
    • 물론, 두 번째 예외 대신 첫 번째 예외를 기록하도록 코드를 수정할 수도 있지만, 코드가 너무 지저분해져 실제로 그렇게까지 하는 경우는 없습니다. 
  • 자원을 하나 더 사용하게 된다면, 코드가 굉장히 지저분해진다. 

자원이 둘 이상이면 try-finally 방식은 너무 지저분하다! (47쪽)

import java.io.*;

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

 

 

해결책: try-with-resource 

해당 구조를 사용하기 위해선, 닫아야 하는 자원이 AutoCloseable 인터페이스를 구현해야 합니다.

  • 해당 인터페이스는 단순히 void를 반환하는 close 메서드 하나만 덩그러니 정의된 인터페이스입니다.
  • 자바 라이브러리와 서드파티 라이브러리들의 수많은 클래스와 인터페이스들은 이미 AutoCloseable을 구현하거나 확장합니다. 
  • 닫아야 하는 자원을 뜻하는 클래스를 작성한다면, AutoCloseable을 반드시 구현해야 합니다.

 

 

try-with-resources - 권장 방식

import java.io.*;

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 문 안에서 예외가 발생하고, (코드에 나타나지 않는) close 호출 모두 예외가 발생하면, close에서 발생한 예외는 숨겨지고 try 문 안에서 발생한 예외가 기록됩니다. 
    • 이처럼 실전에서는 프로그래머에게 보여줄 예외 하나만 보존되고, 여러 개의 다른 예외가 숨겨질 수도 있는데, 이런 예외들도 그냥 버려지지 않고, 스택 추적 내역에 '숨겨졌다(suppressed)'는 꼬리표는 달고 출력됩니다.
    • 또한, 자바 7에서의 Throwable에 추가된 getSuppressed 메서드를 이용하면 프로그램 코드에서 가져올 수도 있습니다.

 

try-with-resources + catch 예제 

try문을 중첩하지 않고도 다수의 예외를 처리할 수 있습니다. 

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

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

 

 


출처

https://www.yes24.com/Product/Goods/65551284

 

이펙티브 자바 Effective Java 3/E - 예스24

자바 플랫폼 모범 사례 완벽 가이드 - Java 7, 8, 9 대응자바 6 출시 직후 출간된 『이펙티브 자바 2판』 이후로 자바는 커다란 변화를 겪었다. 그래서 졸트상에 빛나는 이 책도 자바 언어와 라이브

www.yes24.com