ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 예외처리와 Try With Resources
    Java 2024. 5. 30. 16:04

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

     

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

     

    가장 상위 인터페이스로 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
Designed by Tistory.