유한한 자원을 관리하는 일은 중요합니다.
커넥션, 스트림, 파일 등의 자원을 활용할때 try - catch문을 활용해 자원을 열고 닫아주는데
java7 부터는 try - with - resources 구문을 지원합니다.
어떤 경우에 쓸수 있고, 뭐가 좋은지 알아봅시다!
Resource
Resource 는 시스템을 운영하는데 있어서 메모리나 입출력 장치 등 하드웨어, 소프트웨어 형태로 존재하는 구성요소를 의미한다.
여기서 Resource 를 Java 의 외부 Resource 로서 JVM 바깥의 메모리 이외의 Resource 를 지칭하자.
Java 에서 Resource 를 사용하고 나면 해제를 해주어야 한다.
Resource 를 해제하지 않으면 메모리 누수 및 특정 프로그램의 독점으로 인해 해당 객체가 올바르게 작동하지 않을 수 있다.
Garbage Collection 이라는 자동으로 메모리가 해제되는 기능이 있지만, GC 는 메모리를 해제하는 시간을 예측하기 힘들기 때문에, 할당받은 Resource 의 해제 시점 또한 예측하기 힘들다.
따라서, GC 에 의존하기보다는, Resource 사용이 끝나면 직접 해제해주는 편이 좋다.
자주 사용하는 Resource 관련 객체는 InputStream, OutputStream, java.sql.Connection 이 있다.
각각의 객체들은 Resource 와 연결하여, 외부에서 가져온 기능들을 사용할 수 있게 해준다.
이해를 돕기 위해 위의 객체 대신에, 임의로 2가지 Resource 에 관련된 객체를 정의해 보자.
각 Resource 들은 Javable System 에 하나밖에 존재하지 않으며, 인기가 매우 많아 많은 사용자들이 대기하고 있다.
GC 가 자동적으로 해제해주는 것을 기다리기보단, 다른 사용자들을 위해 빨리 해제하는 방향으로 구현해보자.
public class JavableBook {
public String page(int pageNumber) throws IOException {
// 책에 관련된 로직
}
public void close() throws IOException {
// 책에 대한 Resource 를 해제하는 로직
}
}
public class JavableVideo {
public String scene(int time) thirows IOException {
// 장면에 대한 로직
}
public void close() throws IOException {
// 비디오에 대한 Resource 를 해제하는 로직
}
}
기존의 Resource 를 사용하는 순서
public void play() throws IOException {
JavableBook book = new JavableBook(); // >> (1)
book.page(200); // >> (2)
book.close(); // >> (3)
}
Resource 를 사용할 때에는 먼저 (1) 과정인 해당 Resource 와 객체를 연결해주어야 한다.
그 다음에는 (2) 과정처럼 해당 Resource 를 사용하여, 필요한 로직들을 실행한다.
마지막으로 (3) 과정처럼 해당 Resource 를 해제하면 된다.
간단해 보이지만 이곳에서 문제가 발생할 수 있다.
만약 예외가 발생한다면, 중간의 Resource 가 해제되지 않을 가능성이 있기 때문이다.
예를들면 JavableBook 이라는 책의 페이지가 150쪽이라고 가정하면, (2) 과정을 처리하는 과정에서 예외가 발생한다.
(2) 과정에서 예외가 발생하면 (3) 과정까지 가지 못하고, 예외 처리 로직으로 이동하게 된다.
예외의 발생으로 인해 Resource 가 해제되지 않는 결과가 발생한다.
이 문제점을 해결하기 위해서 나온 방법이 try-finally 방법이다.
try-finally : 예외 처리를 고려한 기존의 Resource 해제 방법
try-finally 는 기존의 블록 단위 예외 처리 방법이다.
원래는 try-catch-finally 블록으로 이루어진다.
● try : 처음에 실행하고 싶은 블록
● catch : 예외 발생시 처리하고 싶은 블록
● finally : 예외 발생 여부에 상관없이 최종적으로 처리하고 싶은 블록
이때 finally 블록을 활용하여 Resource 를 해제해보자.
public void play() throws IOException {
try {
JavableBook book = new JavableBook(); // >> (1)
book.page(200); // >> (2)
} finally {
book.close(); // >> (3)
}
}
위의 예시처럼 try 의 (1), (2) 과정에서 어떠한 오류가 발생하더라도, (3) 과정에서 최종적으로 Resource 를 해제하게 되므로, Resource 해제를 보장할 수 있다.
하지만 Resource 종류를 다양하게 사용하게 되면 다음과 같이 복잡한 경우가 발생하게 된다.
public void play() throws IOException {
try {
JavableBook book = new JavableBook(); // >> B-(1)
try {
JavableVideo video = new JavableVideo(); // >> V-(1)
book.page(150); // >> B-(2)
video.scene(150); // >> V-(2)
} finally {
video.close(); // >> V-(3)
}
} finally {
book.close(); // >> B-(3)
}
}
이렇게 되면 코드가 점점 길어지고 indent 가 깊어지며 지저분해지게 된다.
try-with-resource
Java 7 부터 추가된 try-with-resource 는 앞서 언급한 문제점을 해결할 수 있다.
public void play() throws IOException {
try (
JavableBook book = new JavableBook(); // >> B-(1)
JavableVideo video = new JavableVideo(); // >> V-(1)
) {
book.page(150); // >> B-(2)
video.scene(150); // >> V-(2)
}
}
위의 예시 코드의 특징
- try 바로 다음 소괄호에서 Resource 를 할당받는 (1) 과정을 진행
- 중괄호에서 로직을 실행하게 되는 (2) 과정을 진행
기존의 (3) 과정이 존재하지 않는다.
try-with-resource 에서는 구절이 모두 끝나게 된다면 자동으로 Resource 를 반납하기 때문이다.
하지만 아무 Resource 나 반납이 가능한 것이 아니다.
사용자가 임의로 정의한 Resource 를 할당받는 객체를 사용하는 현재 상태에서는 try-with-resource 가 작동하지 않을 것으로 예상된다.
try-with-resource 를 통하여 해제할 Resource 를 가진 객체는 AutoCloseable 이라는 Interface 를 구현해야 한다.
Interface AutoCloseable
이해를 돕기 위해 try-with-resource 에서 자동으로 해제되는 FileInputStream 소스코드 일부를 예시로 보자.
public class FileInputStream extends InputStream
abstract class InputStream implements Closeable
public interface Closeable extends AutoCloseable { // Closeable 소스코드 일부
public void close() throws IOException;
}
public interface AutoCloseable { // AutoCloseable 소스코드 일부
void close() throws Exception;
}
FileInputStream 에서는 Closeable 를 구현하고 있다.
Closeable 의 경우에는 try-with-resource 를 사용할 수 있게 해주는 AutoCloseable 를 상속받아서 사용하고 있다.
이 때문에 try-with-resource 에 필수적인 close 메소드가 각각의 Resource 에 관련된 객체의 특성에 맞게 구현되어 있다.
사용자가 임의로 정한 객체에도 다음과 같이 AutoCloseable 라는 인터페이스의 close 메소드를 구현하게되면 try-with-resource 를 사용할 수 있게 된다.
// try-with-resource 사용을 위한 JavableBook 추가 구현
public class JavableBook implements AutoCloseable {
@Override
public void close() throws IOException {
// 책에 대한 자원을 해제하는 로직
}
}
결론
코드가 복잡해질수록 Resource 사용 방향 및 방법 뿐만 아니라 해제 및 예외 처리에 대해서도 고민이 많아진다.
이전의 방법보다는 try-with-resource 방법과 함께 Resource 를 자동으로 해제하고, catch 에서 예외처리를 해주면 깔끔하면서도 더 견고한 코드가 완성된다.
참조
- try-with-resource Oracle 공식문서
- 조슈아 블로크, “이펙티브 자바”, 인사이트(2018), 47-50
- https://tecoble.techcourse.co.kr/post/2021-04-26-try-with-resource/