Effective Java

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

takman 2022. 1. 11. 17:05

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

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

 

자원 닫기는 클라이언트가 놓치기 쉽기 때문에 예측할 수 없는 성능 문제로 이어지기도 한다.

이런 자원 중 상당수가 안전망으로 finalizer를 활용하고 있지만 믿을만 하지 못하다(아이템8 참고)

 

전통적으로 자원이 제대로 닫힘을 보장하는 수단으로 try-finally가 쓰였다.

 

try-finally

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


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

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 블록 모두에서 발생할 수 있다는 것이다.
(close 메서드를 호출할 때)

또한, try-finally를 중첩하는 경우 코드가 너무 지저분해져서 가독성이 떨어지게 된다.

 

해결책:  try-with-resources

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

(cf. AutoCloseable : 단순히 void를 반환하는 close 메서드 하나만 정의한 인터페이스)

닫아야 하는 자원을 뜻하는 클래스를 작성한다면 AutoCloseable을 반드시 구현해야한다.

public static String firstLineOfFile(String path) throw 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);
	}
}


이때, 실전에서는 프로그래머에게 보여줄 예외 하나만 보존되고 여러 개의 다른 예외가 숨겨질 수도 있다.
이렇게 숨겨진 예외들은 스택 추적 내역에 숨겨졌다(suppressed)는 꼬리표를 달고 출력된다.
또한, 자바 7에서 Throwable 에 추가된 getSuppressed 메서드를 이용하면 프로그램 코드에서 가져올 수 있다.

+ try-with-resources에서도 catch 절을 쓸 수 있다.

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



 

결론

꼭 회수해야 하는 자원을 다룰 때 try-finally 말고, try-with-resources를 사용하자.
예외는 없다. 코드는 더 짧고 분명해지고, 만들어지는 예외 정보도 훨씬 유용하다.
또한 정확하고 쉽게 자원을 회수할 수 있다.