예외처리와 Try With Resources

2024. 5. 30. 16:04·Java

응용프로그램에서 문제가 발생하면 보통 에러 혹은 예외를 던진다.

 

자바의 에러 구조는 다음과 같다.

 

가장 상위 인터페이스로 Throwable 이 있고

여기서 Throwable을 상속받는 두가지로 예외는 Exception, 에러는 Error로 나뉜다.

 

그리고 Exception을 상속받는 클래스 중 RuntimeException은 런타임 도중 발생하는 예외를 잡는다.

 

예외는 던지거나 잡을 수 있다.

던지고싶다면 메소드 선언부에서 throws 키워드로 던지고싶은 Exception을 나열하면된다.

잡으려면 try-catch~finally가 있다.

 

try ~ catch ~ finally에서 중요한 점은

Exception이 catch 되더라도 finally는 발동한다.

public int test(){
    try{
        Class.forName("java.lang.String");
        return 12;
    }catch(ClassNotFoundException e){
        e.printStackTrace();
    }finally{
        // finally 구문은 Exception이 발생해도 반드시 실행한다.
        return 10;
    }
}

Try with Resources

https://mangkyu.tistory.com/217

다음으로는 try with resources 이다.

이는 주로 FileInputStream같은 열어주면? 닫아줘야하는 객체가 있을 때,

Auto로 finally없이 작업이 끝나면 닫아주는 역할을 수행한다.

사용법

try with resources 를 사용하려면 AutoCloseable 인터페이스의 close() 메서드를 구현해야 한다.

아마 try절에 소괄호 속에 들어가는 객체에 close메서드를 호출해서 그런게 아닐까 싶다.

 

참고) 자바 9에서 부터는 아래와 같이 객체 생성부와 try속 객체를 넣는곳을 분리할 수 있다.

참고2) 아래와 같이 사용하려면 AutoCloseable을 구현한 객체가 final이거나 effectively final이어야 한다.

void test(){
  MyResource myRes1 = new MyResource();
  MyResource myRes2 = new MyResource();
  try(myRes1;myRes2){ // Java 9부터 가능
	  // 실행되어야 하는 구문
  }catch(Exception e){
	  // catch절 구문
  }
}

이러한 try with resources를 사용하는데 있어서 크게 두가지 이유를 위 블로그에서 참조할 수 있었다.

스택 트레이스 누락

원래라면 다음과 같은 코드가 일반적일 것 이다.

class MyResource implements AutoCloseable{
    // AutoCloseable을 구현해서 close 함수를 구현한다.
    public void open(){
        System.out.println("열어요");
        throw new IllegalArgumentException();
    }

    @Override
    public void close(){
        System.out.println("닫아요");
        throw new IllegalArgumentException();
    }
}

public class Main {

    MyResource myRes1;
    MyResource myRes2;
    public void temp(){
        try{
            myRes1 = new MyResource();
            myRes1.open();
        }finally{
            if(myRes1 != null){
                myRes1.close();
            }
        }
    }
    public static void main(String[] args){
        Main main = new Main();
        main.temp();
    }
}

이렇게 작성하고 실행하면 open에 발생하는 Exception이 스택 트레이스에 잡히지 않는다.

그런데 아래와 같이 바꾸면 결과에 두가지 Exception이 모두 잡히는것 처럼 보인다.

class MyResource implements AutoCloseable{

    public void open(){
        System.out.println("열림");
        throw new IllegalArgumentException();
    }
    @Override
    public void close() {
        System.out.println("닫힘");
        throw new IllegalArgumentException();
    }
}
public class Main {

    MyResource myRes1;
    MyResource myRes2;

    public void test(){
        myRes1 = new MyResource();
        try{
           myRes1.open();
        } catch(Exception e){
          e.printStackTrace();
        } finally {
            if(myRes1 != null) myRes1.close();
        }
    }

    public static void main(String[] args) {
        Main main = new Main();
        main.test();
    }
}
열림
닫힘
java.lang.IllegalArgumentException
	at MyResource.open(Main.java:8)
	at Main.test(Main.java:25)
	at Main.main(Main.java:37)
Exception in thread "main" java.lang.IllegalArgumentException
	at MyResource.close(Main.java:13)
	at Main.test(Main.java:30)
	at Main.main(Main.java:37)

두가지 Exception이 모두 잡힌것 처럼 보이지만

main 메서드를 실행하는 쓰레드에서 Exception이 발생하였다고 나온다.

즉, finally에서 던져진 예외가 catch에서 처리되지 못하고, JVM에 던지게 된 것이다.

이걸 막기위해서 try with resources를 사용한다면

public void temp(){
    MyResource myRes1 = new MyResource();
    try(myRes1){
        myRes1.open();
    }catch(Exception e){
        e.printStackTrace();
    }
}

open과 close에 발생하는 모든 Exception이 catch절에 의해 잡히게 된다.

열어요
닫아요
java.lang.IllegalArgumentException
	at MyResource.open(Main.java:11)
	at Main.temp(Main.java:27)
	at Main.main(Main.java:34)
	Suppressed: java.lang.IllegalArgumentException
		at MyResource.close(Main.java:17)
		at Main.temp(Main.java:26)
		... 1 more

자원 반납 누락

이제 위의 코드에서 open에 있는 throw를 없애고, 두개의 MyResource객체를 만들어서 finally구문에서 close하자.

class MyResource implements AutoCloseable{

    public void open(){
        System.out.println("열림");
    }
    @Override
    public void close() {
        System.out.println("닫힘");
        throw new IllegalArgumentException();
    }
}
public class Main {

    MyResource myRes1;
    MyResource myRes2;

    public void test(){
        myRes1 = new MyResource();
        myRes2 = new MyResource();
        try{
           myRes1.open();
           myRes2.open();
        } catch(Exception e){
          e.printStackTrace();
        } finally {
            if(myRes1 != null) myRes1.close();
            if(myRes2 != null) myRes1.close();
        }
    }

    public static void main(String[] args) {
        Main main = new Main();
        main.test();
    }
}

스택 트레이스는 다음과 같이 나온다.

열림
열림
닫힘
Exception in thread "main" java.lang.IllegalArgumentException
	at MyResource.close(Main.java:12)
	at Main.test(Main.java:29)
	at Main.main(Main.java:36)

finally 에 있는 myRes1의 close에서 발생한 Exception이 처리되지 못한채로 JVM까지 처리 위임이 되었기에

비정상종료되어 myRes2의 닫힘이 발생할 수 없게 되었다.

 

이제 try with resources로 변경해보자.

public void test(){
    MyResource myRes1 = new MyResource();
    MyResource myRes2 = new MyResource();
    try(myRes1;myRes2){
        myRes1.open();
        myRes2.open();
    }catch(Exception e){
        e.printStackTrace();
    }
}

스택 트레이스는 다음과 같이 달라진다.

열림
열림
닫힘
닫힘
java.lang.IllegalArgumentException
	at MyResource.close(Main.java:12)
	at Main.test(Main.java:23)
	at Main.main(Main.java:30)
	Suppressed: java.lang.IllegalArgumentException
		at MyResource.close(Main.java:12)
		at Main.test(Main.java:20)
		... 1 more

이제 myRes1이 Exception을 던지더라도 myRes2까지 실행하는데 영향을 주지않았고

각 close() 메서드 호출동안에 발생하는 Exception또한 catch절에 잘 잡히게 되었다.

 

 

그 원리는 bytecode를 까보면 알 수 있다.

 

내부적으로 작성자가 작성한 catch절을 제외하고도 exception이 발생할 부분들에 대하여 컴파일러가 try-catch로 감싸놓고

 

Throwable에 있는 addSuppressed를 통해 누락되지 않도록 안쪽부터 발생한 Exception을 전부 누적해놓는다.

public void temp3() {

    // try with resources 사용 (JAVA 9에서는 아래와 같이 AutoCloseable을 구현한 객체를 바로 넣을 수 있음)
    // suppressed를 통해 누락없이 예외를 잡을 수 있음
    // myRes1 = new MyResource(); 이렇게 작성하면 effectively final이어야 한다는 에러발생
    MyResource myRes1 = new MyResource();
    try(myRes1) {
        myRes1.open();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

// 위의 코드를 bytecode로 보면 (인텔리제이 기준이다.)'

public void temp3() {
    MyResource myRes1 = new MyResource();
    
    // 순서상으로 누적되어야 하는건 open() 메소드 이후 close()메소드 이기 때문에 내부적으로 감싸준듯 하다.

    try {
        MyResource var2 = myRes1;

        try {
            myRes1.open();
        } catch (Throwable var6) {
            if (myRes1 != null) {
                try {
                    var2.close();
                } catch (Throwable var5) {
                    var6.addSuppressed(var5);
                }
            }

            throw var6;
        }

        if (myRes1 != null) {
            myRes1.close();
        }
    } catch (Exception var7) {
        var7.printStackTrace();
    }

}

 

 

'Java' 카테고리의 다른 글

함수형 인터페이스  (1) 2024.04.18
가비지 컬렉션 - 가비지 컬렉션의 개념와 작동 방식  (1) 2024.04.13
'Java' 카테고리의 다른 글
  • 함수형 인터페이스
  • 가비지 컬렉션 - 가비지 컬렉션의 개념와 작동 방식
devKhc
devKhc
  • devKhc
    개발저장소 by 회창
    devKhc
  • 전체
    오늘
    어제
    • 분류 전체보기 (24)
      • Web-Spring (7)
      • CS (5)
      • Infra (3)
      • DB (1)
      • Java (3)
      • CodeTree (5)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    코드트리
    Nginx #proxy #리버스 프록시
    다중 코어 시스템
    모드비트
    JsonTypeInfo
    Multi-Processor
    인터럽트 #운영체제 #공룡책
    set-cookie
    Springsecurity
    defaultoauth2authorizationrequestresolver
    SpringBoot
    코딩테스트
    이중모드와 다중모드
    samesite=none
    JsonSubTypes
    try with resources
    코드트리조별과제
    운영체제
    springboot #jenkins #CI/CD #Docker #EC2
    인터럽트
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
devKhc
예외처리와 Try With Resources
상단으로

티스토리툴바