기록하기

try-with-resources 사용법 예시와 AutoCloseable 구현 본문

language/java

try-with-resources 사용법 예시와 AutoCloseable 구현

jjungdev 2023. 9. 16. 12:12

File I/O

자바의 I/O 는 기본적으로 InputStream, OutputStream 이라는 abstract 클래스가 제공된다.

어떤 파일을 읽을 때 InputStream 의 자식 클래스로 읽고, 데이터를 쓸 때에는 OutputStream 의 자식 클래스로 쓰면 된다.

 

이때 꼭 기억해야 하는 메소드는 read()close() 이며 특히 close() 의 경우 리소스를 닫을 때 사용하는 메소드로 매우 중요하다. 결국 '자원'을 쓰는 것이므로 close 를 해줘야 하며 try-catch-finally 에서는 finally 에서 close() 처리를 해줄 수 있었다.

 

test.txt

더보기

Java File I/O test file

 

try-catch-finally

public class TryCatchExample {

    public static void main(String[] args) throws IOException {
        FileInputStream fileInputStream = null;
        BufferedInputStream bufferedInputStream = null;

        try {
            fileInputStream = new FileInputStream("test.txt");
            bufferedInputStream = new BufferedInputStream(fileInputStream);

            int i = -1;
            while ((i = bufferedInputStream.read()) != -1) {
                System.out.print((char) i);
            }

        } catch (IOException e) {
            //log.error(e);
        } finally {
            if (bufferedInputStream != null) {
                bufferedInputStream.close();
            }
            if (fileInputStream != null) {
                fileInputStream.close();
            }
        }
    }
}

 

 

그런데 이 경우 아래와 같은 단점이 있다.

  • 유한한 자원인 FileDescriptor 를 할당 받아서 처리하다보니 자원을 선점한 뒤 close 를 하지 않으면 결국 memory leak 이 발생하게 된다.
  • 또한 ouput 의 경우에는 buffer 가 있는데 어느 정도 채워진 상태에서 close 를 안 하게 되면 flush 를 하지 않아 기록이 안 되는 문제가 발생할 수도 있다.


즉, close() 를 호출하여 닫아줘야 하는데, 

 

  • try-catch-finally 에서는 null 체크 후에 직접 호출을 해줘야하는 문제도 있고, 실수 혹은 에러로 인해 자원이 반납되지 않을 수 있다.
  • 그리고, InputStream, OutputStream 둘 다 계층구조라서 하위 것을 안 닫고 중간 것을 닫으면 문제가 발생한다.

 

이런 단점을 해결하고자 Java7 에서는 try-with-resources 가 추가되었는데, try-with-resources 에 대해서 간단하게 살펴보고 AutoCloseable 구현 클래스를 생성해보도록 하겠다.

 

try-with-resources

try-with-resources 사용방법은 아래와 같다.

public class TryWithResourcesExample {

    public static void main(String[] args) {
        try (FileInputStream fileInputStream = new FileInputStream("test.txt");
             BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream)
        ) {
            int i = -1;
            while ((i = bufferedInputStream.read()) != -1) {
                System.out.print((char) i);
            }
        } catch (IOException e) {
            //log.error(e);
        }
    }
}

 

try 블록에 괄호를 추가해서 파일 input 과 관련된 명령어를 작성하면, try 블록이 끝날 때 자동으로 파일을 닫거나 자원을 해제해준다.


하지만, 한 가지 주의해야할 것은, try-with-resources 를 다 사용할 수 있는 것이 아니라 AutoCloseable 을 구현하고 있는 자원 클래스들에서 사용할 수 있다는 점이다.

만약 그렇지 않은 경우에서 사용하고 싶다면 직접 구현하는 클래스를 설계해줘야하는데 예시는 다음과 같다.

 

CustomResource

public class CustomResource implements AutoCloseable {

    public void doSomething() {
        System.out.println("doSomething");
    }

    @Override
    public void close() throws Exception {
        System.out.println("close");
    }
}

 

구현 클래스에서는 close() 메소드를 꼭 override 하여 재정의를 해줘야 한다. 간단하게 close 문구를 출력해봄으로써 동작 여부를 확인할 수 있다.

 

try-with-resources

public class TryWithResourcesExample {

    public static void main(String[] args) {
        try (CustomResource customResource = new CustomResource()) {
            customResource.doSomething();
        } catch (IOException e) {
            //log.error(e);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

 

실제로 실행을 해보면, 아래와 같이 출력된다.

 

한 가지 더 확인을 해보고 싶은 게 있는데 try-with-resources 를 사용하게 되면 기존에 try-catch-finally 에서 발생한 에러 스택 트레이스가 누락되는 문제를 해결할 수 있다고 하여 에러를 발생시켜서 확인을 해보려고 한다.

 

 

먼저, try-catch-finally 와 CustomResource 를 활용하여 에러를 발생시켜보자

 

CustomResource

public class CustomResource implements AutoCloseable {

    public void doSomething() {
        System.out.println("doSomething");
        throw new IllegalStateException(); //추가
    }

    @Override
    public void close() throws Exception {
        System.out.println("close");
        throw new IllegalStateException(); //추가
    }
}

 

try-catch-finally

public class TryCatchExample {

    public static void main(String[] args) throws Exception {
        CustomResource customResource = null;

        try {
            customResource = new CustomResource();
            customResource.doSomething();
        } finally {
            if (customResource != null) {
                customResource.close();
            }
        }
    }
}

 

일부로 doSomething(), close() 두 곳에서 exception 을 발생시켰기 때문에 에러 스택 트레이스는 이 두 곳이 모두 찍혀야 한다. 하지만 결과는..

close() 에서 발생한 에러만 찍히는 것을 알 수 있다. 이 경우 doSomething() 에서 생긴 에러는 찍히지 않아, 원인 파악이 힘들어질 것이다.

만약 try-with-resources 를 활용하게 되면 어떻게 될까?

 

try-with-resources

이와 같이 doSomething, close 메소드에서 발생한 모든 에러가 찍히는 것을 확인할 수 있고, 이를 통해 에러 원인 파악면에서도 try-with-resources 를 사용해야함을 알 수 있다.

 

자원 해제 및 반납, 에러 스택 트레이스 등의 관점에서 볼 때 try-catch-finally 가 아닌 try-with-resources 를 활용해야함을 알 수 있었고, AutoCloseable 을 직접 구현하여 사용할 수 있음을 알게 되었다.

'language > java' 카테고리의 다른 글

String Constant Pool 과 equals, hashCode  (0) 2023.09.07
Java의 GC 진행 방법  (0) 2023.08.18
List, Set, Map 의 차이  (0) 2022.06.11