try - with - resources 는 try(..) 문에서 선언된 객체들에 대해서 try가 종료될 때 자동으로
자원을 해제해주는 기능이다. 주로 외부 자원인 파일 관련 객체와 socket Handler 객체와 같은
자원들은 try - catch - finally 문을 사용하여 마지막에 다 사용한 자원을 해제한다.
AutoCloseable은 try에 선언된 객체가 AutoCloseable을 구현했더라면 Java는 try구문이 종료될 때
객체의 close() 메소드를 호출해 준다.
Java6에서 리소스 사용 및 해제하는 방법을 한번 살펴보고, try-with-resources로 동일한 코드를
리팩토링해보자.
1. AutoCloseable 사용 방법
다음은 try - catch - finally 를 사용하여 파일을 열고 문자열을 모두 출력하는 코드이다.
public static void main(String[] args) throws IOException {
FileInputStream is = null;
BufferedInputStream bis = null;
try {
is = new FileInputStream("/Users/JongHyun-Jung/test.txt");
bis = new BufferedInputStream(is);
int n = -1;
while((n = bis.read()) != -1) {
System.out.println((char) n);
}
} finally {
if(is != null){
is.close();
}
if(is != null){
bis.close();
}
}
}
try 에서 inputStream 객체를 생성하고 finally에서 close를 한다.
try 안 코드를 실행하다 Exception 발생 시 모든 코드가 실행되지 않을 수 있기 때문에
finally에 close 코드를 넣어준다.
심지어 inputStream 객체가 null인지 체크해주어야 하고 close에 대한 Exception 처리도 해주어야 한다.
main에서 IOException을 throws한다고 명시적으로 선언하면 close에 대한 try-catch구문은 생략한다.
2. Try - with - resources 방식으로 자원 쉽게 해제
다음은 Try-with-resources를 사용하여 InputStream으로 파일의 문자열을 모두 출력한다.
public class TraditionalResourceCloseable {
public static void main(String[] args) throws IOException {
// try - with - resources로 자원 해제
try (
FileInputStream is = new FileInputStream("/Users/jonghyun-jung/test.txt");
BufferedInputStream bis = new BufferedInputStream(is);
){
int n = -1;
while((n = bis.read()) != -1){
System.out.println((char) n);
}
} catch (IOException e){
e.printStackTrace();
}
}
}
코드를 보면, try(...)안에 InputStream 객체를 선언 및 할당한다.
여기에 선언한 변수들은 try 안에서만 사용이 가능하다. 코드 실행위치가 try문을 벗어나면
try - with - resources 변수들은 try 안에서 사용할 수 있다. 코드의 실행 위치가 try 문을 벗어나면
try - with - resources 는 try(...)안에 선언된 객체의 close() 메소드를 호출해야 한다.
그래서 fianlly에 close()를 명시적으로 호출해줄 필요가 없다.
try - with - resources 에서 자동으로 close가 호출되는 것은 AutoCloseable을 구현한 객체에만 해당된다.
try - with - resources 의 장점은 코드를 짧고 간결하게 만들어 읽고 쉽고 유지보수가 쉽다.
또한 명시적으로 close를 호출하여면 많은 if와 try-catch를 사용해야 하기 때문에 실수로 close를 빼먹는
경우가 있는데, 해당 방식을 활용하면 자잘한 버그들이 발생할 가능성을 줄여준다.
3. Try - with - resources 방식으로 close() 메소드 호출되는 객체
Try - with - resources 가 모든 객체의 close()를 호출해주지 않는다.
AutoCloseable 을 구현한 객체에만 close()가 호출된다.
public interface AutoCloseable {
void close() throws Exception;
}
주의할 점은 BufferedInputStream 객체는 InputStream 객체를 상속받습니다.
만약에 아래 코드와 같이 InputStream 객체가 AutoCloseable를 상속받은 Closeable을 구현하였을 때
BufferedInputStream 객체가 Try-with-resources에 의해서 해제될 수 있습니다.
public abstract class InputStream extends Object implements Closeable {
...
}
public interface Closeable extends AutoCloseable {
void close() throws IOException;
}
4. AutoCloseable 구현 클래스 만들기
내가 만든 클래스가 try - with - resources로 자원이 해제되길 원한다면 AutoCloseable을 implements 해야한다.
아래 코드에서 CustomResource 클래스는 AutoCloseable을 구현하였다.
main에서는 이 객체를 try - with - resources로 사용하고 있다.
public class AutoCloseableExample {
public static void main(String[] args){
try (CustomResource customResource = new CustomResource()){
customResource.doSomething();
} catch (Exception e){
e.printStackTrace();
}
}
}
public class CustomResource implements AutoCloseable {
public void doSomething(){
System.out.println("Do Sth..");
}
@Override
public void close() throws Exception {
System.out.println("CustomResource Closed!");
}
}
실행하면 다음과 같이 출력된다.
Do Sth..
CustomResource Closed!
Process finished with exit code 0
참고로 close 메서드 구현시 구체적인 exception을 throw 하고, close 동작이 전혀 실패할 리가 없을 때는
exception을 던지지 않도록 구현하는 것을 강력하게 권고하고 있습니다.
특히 close 메서드에서 InterruptedException을 던지지 않는 것을 강하게 권고합니다.
InterruptedException은 쓰레드의 인터럽트 상태와 상호작용 하므로 interruptedException이 억제되었을 때
런타임에서 잘못된 동작이 발생할 수 있기 때문입니다.
참조 : AutoCloseable 클래스 (velog.io)