본문 바로가기
JAVA

[JAVA] try-with-resources 사용하기(try-catch-finally 와 비교)

by 무사뎀벨레 2024. 1. 18.

 

 

 

 

 

일반적으로 자원(resource)을 사용하고 난 뒤에는 사용한 자원을 해제해야 합니다.
그렇지 않으면 자원 누수가 발생하며 메모리 부족과 같은 상황이 발생할 수 있습니다.

 

자원을 해제할 때 try-catch-finally 구문을 이용하곤 했지만, JAVA7 버전부터는
try-with-resourse 구문을 이용하여 자원해제 처리를 할 수 있습니다.

 

 

 

 

 

 

 

1. try-catch-finally를 이용한 자원 해제


사용 후에 자원 해제해주어야 하는 자원들은 Closable 인터페이스를 구현하고 있으며, 사용 후에 close 메소드를 호출해주어야 했습니다.

 

JAVA7 이전에는 close 메소드를 호출하기 위해try-catch-finally를 이용해서 Null 검사와 함께 직접 호출해야 했는데, 대표적으로 파일의 내용을 읽는 경우를 아래 코드와 같이 구현할 수 있습니다.

public static void main(String args[]) throws IOException {
    FileInputStream fis = null;
    BufferedInputStream bis = null;
    try {
    	// 자원 생성
        fis = new FileInputStream("file.txt");
        bis = new BufferedInputStream(fis);
        int data = -1;
        while((data = bis.read()) != -1){
            System.out.print((char) data);
        }
    } finally {
        // close resources(자원 해제 처리)
        if (fis != null) is.close();
        if (bis != null) bis.close();
    }
}

 

위와 같이 try-catch-finally 구문을 이용한 자원 해제의 방법다음과 같은 단점을 가지고 있습니다.

1. 자원 해제에 의해 코드가 복잡해짐
2. 작업이 번거로움
3. 실수로 자원을 해제하지 못하는 경우 발생
4. 에러로 자원을 해제 하지 못하는 경우 발생
5. 에러 스택 트레이스가 누락되어 디버깅이 어려움

위와 같은 문제들로 인해 JAVA7부터는 자원해제를 위해 try-with-resources 구문을 사용할 수 있습니다.

 

 

 

 

 

 

 

2. try-with-resources 이용한 자원 해제


JAVA7부터는 AutoCloseable 인터페이스를 구현하고 있는 자원에 대해 try-with-resources 구문을 적용 가능하도록 하였고, 이를 통해 코드가 유연해지고, 누락되는 에러 없이 모든 에러를 잡을 수 있게 되었습니다.

 


AutoCloseable 인터페이스를 상속받지 않는 클래스로 자원을 생성하면,
기존의 
try-with-resources 구문을 사용하여 자원해제를 처리해야 합니다.

 

사용 방법은 간단합니다.

 

try블록에서 리소스를 선언하고 try블록이 종료되면 자동으로 해제(close)해주기 때문에 따로 자원해제를 할 필요가 없습니다. 아래 코드를 통해 자세하게 사용방법을 살펴볼 수 있습니다.

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

    //try구문에서 자원 생성(FileInputStream과 BufferedInputStream)
    try (
    	FileInputStream is = new FileInputStream("file.txt"); 
    	BufferedInputStream bis = new BufferedInputStream(is)
    ) {
        int data;
        while ((data = bis.read()) != -1) {
            System.out.print((char) data);
        }
    }
    //try문구가 종료되는 지점에서 자동으로 자원 해제 처리
}

 

 

 

 

 

cf) Closeable과 AutoCloseable

기존의 Cloesable에 부모 인터페이스인 AutoCloesable을 새로 추가하였다, 만약 Cloesable을 부모로 만들었다면 기존에 만들어준 클래스들이 모두 Cloesable이 아닌 AutoCloesable를 구현(implements) 하도록 수정이 필요했을 것입니다.

 

이러한 구조 덕분에 기존에 구현된 자원 클래스들 모두 try-with-resources가 사용가능해졌습니다.

public interface Closeable extends AutoCloseable {
    public void close() throws IOException;
}

public interface AutoCloseable {
    void close() throws Exception;
   
}

 

 

 

 

 

 

 

 

3. try-catch-finally가 아닌 try-with-resources를 사용해야 하는 경우


 

1) 에러 스택 트레이스가 누락되는 경우

try-catch-finally를 이용하면 에러가 발생해도 에러 스택 트레이스가 누락되는 경우가 발생할 수 있는데, 이는 디버깅 및 원인 파악을 매우 어렵게 만들 수 있습니다. 아래의 코드로 그 예로 확인할 수 있습니다.

public class TestResource implements AutoCloseable {

    @Override
    public void close() throws RuntimeException{
        System.out.println("TestResource - Close");
        throw new IllegalStateException();
    }

    public void test1() {
        System.out.println("TestResource - test1");
        throw new IllegalStateException();
    }
    
    //try-catch-finally 사용
    public void tempTryCatchFinally() {
        TestResource Resource1 = null;
        try {
            Resource1 = new TestResource();
            System.out.println("TestResource - tempTryCatchFinally Start");
            Resource1.test1();
        } finally {
            if (Resource1 != null) {
                Resource1.close();
            }
        }
    }
    
    //try-with-resource 사용
    public void tempTryWithResoures() {
        try (TestResource Resource2 = new TestResource()) {
            System.out.println("TestResource - tempTryWithResoures Start");
            Resource2.test1();
        }
    }
    
}

 

위 코드는 test1close 호출 시에 런타임 예외 중 하나인 IllegalStateException이 발생하도록 하였습니다.

 

아래는 위의 자원을 각각 try-catch-finallytry-with-resources로 해제해 보는 코드를 실행한 결과입니다.

 

아래의 로그에서 확인할 수 있듯이,

 

try-catch-finally 구문을 사용한 자원 해제는 test1에서 발생한 에러 트레이스가 정상적으로 찍히지 않았습니다. 
이런 경우 에러원인을 파악하기 힘든 경우가 생길 수 있습니다.
반면에, try-with-resources 구문을 사용한 자원해제에러가 발생하는 test1과 close메소드 모두 에러 트레이스가 남게 되었습니다.

//try-catch-finally구문을 사용한 tempTryCatchFinally을 실행한 결과
TestResource - tempTryCatchFinally Start
TestResource - test1
TestResource - Close

java.lang.IllegalStateException
	at com.example.testing.composition.TestResource.close(TestResource.java:8)
	at com.example.testing.composition.SteakTest.tempTryCatchFinally(SteakTest.java:49)
    
    
    

//try-with-resource구문을 사용한 tempTryCatchFinally을 실행한 결과
TestResource - tempTryWithResoures Start
TestResource - test1
TestResource - Close

java.lang.IllegalStateException
	at com.example.testing.composition.TestResource.close(TestResource.java:8)
	at com.example.testing.composition.SteakTest.tempTryCatchFinally(SteakTest.java:49)
    ......
    
    
    Suppressed: java.lang.IllegalStateException
		at com.example.testing.composition.TestResource.close(TestResource.java:8)
		at com.example.testing.composition.SteakTest.tempTryWithResoures(SteakTest.java:68)

 

 

 

 

2) 에러로 자원을 해제하지 못하는 경우

try-with-resources를 사용해야 하는 이유로는 에러 스택 누락을 방지하는 것도 있지만, 누락 없이 모든 자원을 해제할 수 있다는 점도 있습니다.

 

public class TestResource implements AutoCloseable {

    @Override
    public void close() throws RuntimeException{
        System.out.println("TestResource - Close");
        throw new IllegalStateException();
    }

    public void test2() {
        System.out.println("TestResource - test2");
    }
    
    //try-catch-finally 사용
    public void temp2TryCatchFinally() {
        TestResource resource1 = null;
        TestResource resource2 = null;

        try {
            resource1 = new TestResource();
            resource2 = new TestResource();
            System.out.println("TestResource - temp2TryCatchFinally Start");
            resource1.test2();
            resource2.test2();
        } finally {
            if (resource1 != null) {
                resource1.close();
            }

            if (resource2 != null) {
                resource2.close();
            }
        }
    }
    
    //try-with-resource 사용
    public void temp2TryWithResoures() {
        try (TestResource resource1 = new TestResource(); TestResource resource2 = new TestResource()) {
		System.out.println("TestResource - temp2TryWithResoures Start");
        resource1.test2();
		resource2.test2();
	}
    }
    
}

 

위 코드는 자원 해제 누락을 비교하는 코드입니다. try-catch-finally구문과 try-with-resource 두 가지의 방법을 비교합니다.

위 코드를 실행시켜 자원을 해제하면 아래와 같은 로그를 확인할 수 있습니다.

 

//try-catch-finally 사용
TestResource - temp2TryCatchFinally Start
TestResource - test2
TestResource - test2
TestResource - Close

//try-with-resource 사용
TestResource - temp2TryWithResoures Start
TestResource - test2
TestResource - test2
TestResource - Close
TestResource - Close

 

먼저 try-catch-finally구문을 이용한 자원해제 로그를 살펴보면 두 번째 자원인 resource2가 정상적으로 해제되지 않은 걸 확인할 수 있습니다.

이러한 문제가 발생한 이유는 resource1을 close하는 finally 구문 안에 IllegalStateException를 catch하는 코드가 없기 때문입니다. 이러한 문제를 해결하기 위해서는 resource1과 resource2를 해제하는 부분을 각각 try-catch-finally로 묶어 처리해야 하는데, 이는 코드를 더욱 복잡하게 만들 수 있습니다.

 

반면, try-with-resource구문의 자원해제 로그를 살펴보면 누락 없이 정상적으로 2가지 자원이 모두 해제된 것을 확인할 수 있습니다. 이러한 이유는 Java 파일이 Class 파일로 컴파일될 때 try-with-resources에서 누락 없이 모든 경우를 try-catch-finally로 변환해 주기 때문이고 다시 말해, try-with-resource구문은 try-catch-finally 구문에서 직접 구현해주어야 했던 부분을 컴파일러가 처리해 준 것입니다.

반응형

댓글