InputStream
, OutputStream
, java.sql.Connection
등은 close
메서드를 호출해 닫아줘야한다. 하지만 이걸 클라이언트가 놓치면 성능 문제가 생긴다. 안전망으로 finalizer
가 있지만, 그리 믿을만하지 못하다 (아이템8).
public static String inputString() throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String result = br.readLine();
br.close();
return result;
}
위 방법대로 close를 호출하게 되면 문제가 발생할 수 있다. BufferedReader는 사용 중 IOException이 발생할 수 있는데, 만약 br.readLine()
메서드에서 IOException
이 발생하게 되면 메서드가 종료되므로 close
가 호출되지 않고 스트림이 메모리에 남아있게 된다.
전통적으로 try-finally 문을 사용해서 close 처리를 해주었다.
public static String inputString() throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
try {
return br.readLine();
} finally {
br.close();
}
}
finally 블록은 try, catch 블록이 끝난 뒤 실행할 로직을 정의해 주는 블록이다. 따라서 이제 IOException이 발생하게 되더라도 상위 메서드로 IOException 객체를 던져준 뒤 finally 메서드를 종료하게 된다.
하지만 try-finally
방식은 자원이 둘 이상이면 코드가 너무 지저분해지고, 실수를 저지를 가능성이 커진다.
public static void inputAndWriteString() throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
try {
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
try {
String line = br.readLine();
bw.write(line);
} finally {
bw.close();
}
} finally {
br.close();
}
}
코드의 지저분함 말고 더 큰 문제도 있다.
public static String inputString() throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
try {
return br.readLine();
} finally {
br.close();
}
}
inputString 메서드의 try 블록을 실행하던 도중 기기에 문제가 생긴다면 readLine이 정상적으로 실행되지 못하고 예외를 던지게 되고, 같은 이유로 finally 블록의 close 메서드도 예외를 던지게 된다.
만약 이 예외들을 catch해서 상위 메서드에서 예외 정보를 체크해본다면, finally 블록에서 터진 예외가 try 블록에서 생긴 예외를 집어 삼켜서 finally 블록의 예외만 체크하게 된다. try 블록에서 터진 예외로 인해 finally 블록에서 예외가 발생했음에도 불구하고 최초 원인인 예외를 체크하지 못하게 되는 것이다. 물론 적절한 코드를 통해 최초 원인 예외를 체크할 수는 있지만, 코드가 너무 더러워지기 때문에 추천하는 방법은 아니다.
이러한 try-finally
방식의 단점을 보완하기 위해 자바 7 버전부터는 try-with-resources
가 도입되었다. try-with-resources
를 사용하기 위해서는 사용하는 자원이 AutoCloseable
인터페이스를 구현해야 한다.
public static String inputString() throws IOException {
try (BufferedReader br = new BufferedReader(new InputStream(System.in))) {
return br.readLine();
}
}
가독성이 좋아지고 예외가 발생했을 때 디버깅 하기에도 더 편리해졌다. inputString 메서드의 readLine과 close 모두에서 예외가 발생하는 경우, close(물론 코드 상으로는 보이지 않지만) 호출 시 발생하는 예외는 숨겨지고 readLine의 예외가 기록된다.
이렇게 숨겨진 예외는 무시되는 것이 아니라, suppressed 상태가 되어 stackTrace 시 숨겨졌다는 메시지로 출력된다. suppressed 상태가 된 예외는 자바 7부터 도입된 getSuppressed 메서드를 통해 가져와서 사용할 수 있다.