resource란 외부의 데이터(DB, Network, File)을 말한다. 이러한 resource들은 자바 내부에 위치한 요소들이 아니므로, 외부 데이터에 접근하려 할 때 예외가 발생할 수 있다.
사용 후에 반납해줘야 하는 자원들은 Closable 인터페이스를 구현하고 있으며, 사용 후에 close() 메소드를 호출해주어야 한다.
자바7 이전에는 close()에서 발생하는 예외 또한 처리하기 위해 아래처럼 코드가 복잡해지는 문제가 존재했다.
public static void main(String[] args) throws IOException {
FileInputStream is = null;
FileOutputStream os = null;
try {
is = new FileInputStream("input.txt");
os = new FileOutputStream("output.txt");
int data = -1;
while ((data = is.read()) != -1) {
System.out.print((char) data);
os.write(data);
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
// close()에 대한 예외처리도 필요
if (is != null) is.close();
try {
os.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
자원 반납에 의한 코드 복잡성 문제 뿐만 아니라 try-catch문은 여러 단점이 존재한다. 에러로 자원을 반납하지 못하는 경우가 발생할 수 있고, 에러 스택 트레이스가 누락되어 디버깅이 어려울 수 있다.
자바는 이러한 문제점을 해결하고자 자바7부터 자원을 자동으로 반납해주는 try-with-resources 문법을 추가하였다. 자바는 AutoCloseable 인터페이스를 구현하고 있는 자원에 대해 try-with-resources를 적용 가능하도록 하였고, 이를 사용함으로써 코드가 유연해지고, 누락되는 에러없이 모든 에러를 잡을 수 있게 되었다.
try 블록에 괄호()를 추가해 자원을 할당하는 코드를 명시하면, 해당 try 블록이 끝나자마자 자동으로 할당된 자원을 해제한다.
public static void main(String[] args) throws IOException {
try (FileInputStream is = new FileInputStream("input.txt");
FileOutputStream os = new FileOutputStream("output.txt")) {
int data = -1;
while ((data = is.read()) != -1) {
System.out.print((char) data);
os.write(data);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
try-catch-finally 문에서 여러 자원을 사용하고 반납하는 경우 자원의 반납이 이루어지지 않을 수 있다.
public class MyResource implements AutoCloseable{
@Override
public void close() throws RuntimeException {
System.out.println("close");
throw new RuntimeException();
}
public void hello() {
System.out.println("hello");
}
}
public static void try1() {
MyResource myResource1 = null;
MyResource myResource2 = null;
try {
myResource1 = new MyResource();
myResource2 = new MyResource();
myResource1.hello();
myResource2.hello();
} finally {
if (myResource1 != null) myResource1.close();
if (myResource2 != null) myResource2.close();
}
}
출력
hello
hello
close
이 문제를 해결하기 위해서는 if문으로 null처리한 close() 메서드를 try-catch문을 통해 catch에서 예외처리를 해야하지만 이는 코드를 복잡하게 만든다.
하지만 try-with-resources문은 별도의 처리없이 문제 해결이 가능하다.
public static void try2() {
try (MyResource myResource1 = new MyResource();
MyResource myResource2 = new MyResource()) {
myResource1.hello();
myResource2.hello();
}
}
출력
hello
hello
close
close
Java 파일이 Class 파일로 컴파일 될 때 try-with-resources에서 누락없이 모든 경우를 try-catch-finally로 변환해주기 때문에 간단하게 문제 해결이 가능하다. 위에서 직접 구현해주어야 했던 부분을 컴파일러가 처리해준 것이다.
try-catch-finally 문을 사용하면 에러가 발생해도 스택 트레이스에 누락되는 경우가 발생할 수 있다.
public class MyResource implements AutoCloseable{
@Override
public void close() throws RuntimeException {
System.out.println("close");
throw new RuntimeException();
}
public void hello() {
System.out.println("hello");
throw new RuntimeException();
}
}
public static void try3() {
MyResource myResource = null;
try {
myResource = new MyResource();
myResource.hello();
} finally {
if (myResource != null) myResource.close();
}
}
try3()를 호출했을 때 예상과는 다르게 hello()에서의 예외는 누락된 채 close()의 예외만 잡히는 것을 볼 수 있다.
하지만 try-with-resources문을 사용하면 누락되는 에러없이 모든 에러가 스택 트레이스에 잡히는 것을 볼 수 있다.
public static void try4() {
try (MyResource myResource1 = new MyResource()) {
myResource1.hello();
}
}
따라서 자원을 반납하는 경우가 생긴다면 코드의 복잡도, 자원 반납 누락, 스택 트레이스 에러 누락 등의 문제가 없는 try-with-resources문을 사용하는 것이 적합하다.