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

item8에서 알 수 있듯이 안전망으로 finalizer를 활용할 수 있으나, finalizer는 확실하게 믿을 순 없다

 

이를 보완하기 위해 try-with-resources를 사용하도록 하자

// 둘 이상의 자원에서 try-finally: 중첩 되어 지저분해 보이고, exception이 일어난 경우 디버깅이 어렵다
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 블록 모두에서 발생할 수 있다.

예를 들어 FileOutputStream 메서드가 예외를 던지고, 같은 이유로 close 메서드도 실패할 때,

두 번째 예외가 첫 번째 예외를 완전히 집어삼켜 버린다. => 스택 추적 내역에 첫 번째 예외에 관한 정보X

 

// try-with-resources: 자원을 회수하는 올바른 선택, 코드는 더 짧고 분명해지며, 예외 케이스도 유용하게 볼 수 있다.
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);
			}
	}
}

Java7 부터 Closeable리소스 와 관련하여 아래와 같이 키워드 뒤의 괄호 안에 리소스를 만들 수 있다.

try (initialize resources here) {
   ...
}

그리고 코드 블록이 완료되면 자동으로 닫히기 때문에 finally가 필요 없다 .

 

이 구조를 사용하려면 해당 자원이 AutoCloseable 인터페이스를 구현해야 한다

AutoCloseable은 단순히 void를 반환하는 close 메서드 하나만 덩그러니 정의한 인터페이스다.

이 경우는 try-finally와는 다르게 양쪽에서 예외가 발생하면, close에서 발생한 예외는 숨겨지고 맨 처음에 발생한 예외가 기록된다.

단, 이렇게 숨겨진 예외들도 그냥 버려지지는 않고, 스택 추적 내역에 ‘숨겨졌다 (suppressed)’는 꼬리표를 달고 출력된다.

 

또한, 자바 7에서 Throwable에 추가 된 getSuppressed 메서드를 이용하면 프로그램 코드에서 가져올 수도 있다.

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-finally에서처 럼 try-with-resources에서도 catch 절을 쓸 수 있다. catch 절 덕분에 try 문을 더 중첩하지 않고도 다수의 예외를 처리할 수 있다.

 

 

참고로, 스트림을 닫을 때에는 IOUtils를 쓰는 방법도 있다. (대신 이 방법을 사용할 경우 Exception이 제대로 나오지 않음)

commons.apache.org/proper/commons-io/apidocs/org/apache/commons/io/IOUtils.html#closeQuietly(java.io.InputStream)

 

IOUtils (Apache Commons IO 2.8.0 API)

Skips characters from an input character stream. This implementation guarantees that it will read as many characters as possible before giving up; this may not always be the case for skip() implementations in subclasses of Reader. Note that the implementat

commons.apache.org

closeQuietly는 Closeable의 varargs 타입을 파라미터로 받고, 이 closeable들을 모두 닫는다.


 

  • try-catch-finally
package com.example.sypark9646.item09;

import static org.apache.tomcat.util.http.fileupload.IOUtils.copy;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class TryCatchFinallyTest {

		private static String root =
				"/Users/soyeon/Documents/GitHub/Effective_Java_Study/sypark9646/src/main/java/com/example/sypark9646/item09";
		private static String inputFileName = root + "/testin.txt";
		private static String outputFileName = root + "/testout_try_catch_finally.txt";

		public static void main(String[] args) throws IOException {

				try {
						final InputStream in = new FileInputStream(inputFileName);
						try {
								final OutputStream out = new FileOutputStream(outputFileName);
								try {
										copy(in, out);
										out.flush();
								} finally {
										out.close();
								}
						} finally {
								in.close();
						}
				} catch (IOException exc) {
						throw new IOException(exc);
				}
		}
}

 

  • try-with-resources
package com.example.sypark9646.item09;

import static org.apache.tomcat.util.http.fileupload.IOUtils.copy;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class TryWithResourcesTest {

		private static String root =
				"/Users/soyeon/Documents/GitHub/Effective_Java_Study/sypark9646/src/main/java/com/example/sypark9646/item09";
		private static String inputFileName = root + "/testin.txt";
		private static String outputFileName = root + "/testout_try_with_resources.txt";

		public static void main(String[] args) throws IOException {

				try (
						final InputStream in = new FileInputStream(inputFileName);
						final OutputStream out = new FileOutputStream(outputFileName);
				) {
						copy(in, out);
						out.flush();
				} catch (IOException exc) {
						throw new IOException(exc);
				}
		}
}

+ Recent posts