[기본 개념] 3 | (1.3) Finally 블럭, Try-with-resources 문

728x90

[기본 개념] 3 | (1.3) Finally 블럭, Try-with-resources 문

1 프로그램 오류

2 예외 클래스의 계층구조

3 예외처리하기 - try-catch문

4 try-catch문에서의 흐름

5 예외의 발생과 catch블럭

6 예외 발생시키기

7> 메서드에 예외 선언하기

8> finally블럭

9> 자동 자원 반환 - try-with-resources문

10 사용자 정의 예외 만들기

11 예외 되던지기(exception re-throwing)

12 연결된 예외(chained exception)

7. 메서드에 예외 선언하기

 예외를 메서드에 선언하는 방법은 메서드의 선언부에 키워드 throws를 사용하여 발생할 수 있는 예외를 적어주면 된다.

 

void method( ) throws Exception1, Exception2, . . . ExceptionN {

        // 메서드의 내용

}

 

 만일 모든 예외의 최고 조상인 Exception클래스를 메서드에 선언하면, 이 메서드는 모든 종류의 예외가 발생할 가능성이 있다는 뜻이다.

 

        void method( ) throws Exception {

            // 메서드 내용

        }

 

이렇게 예외를 선언하면, 그 자손타입의 예외까지도 발생할 수 있다.

 

 사실 예외를 메서드의 throws에 명시하는 것은 예외를 처리하라는 것이 아니라 자신(예외가 발생할 가능성이 있는 메서드)를 호출한 메서드에게 예외를 전달하여 예외처리를 떠맡기는 것이다.

 

예제/ExceptionEx12.java

public class ExceptionEx12 {
    public static void main(String[] args) throws Exception {
        method1();      // 같은 클래스내의 static멤버이므로 객체생성없이 직접호출가능
    }

    static void method1() throws Exception {
        method2();
    }

    static void method2() throws Exception {
        throw new Exception();
    }
}
실행결과

Exception in thread "main" java.lang.Exception
	at ExceptionEx12.method2(ExceptionEx12.java:11)
	at ExceptionEx12.method1(ExceptionEx12.java:7)
	at ExceptionEx12.main(ExceptionEx12.java:3)

 

 프로그램의 실행 도중 java.lang.Exception이 발생하여 비정상적으로 종료했다는 것과 예외가 발생했을 때 호출스택의 내용을 알 수 있다.

 

1 예외가 발생했을 때, 모두 3개의 메서드(main, method1, method2)가 호출스택에 있었다.

2 예외가 발생한 곳은 제일 윗줄에 있는 method2라는 것

3 main메서드가 method1( )을, 그리고 method1( )은 method2( )를 호출하였다.

 

 이처럼 예외가 발생한 메서드에서 예외처리를 하지 않고 자신을 호출한 메서드에게 예외를 넘겨줄 수는 있지만, 어느 한 곳에서는 반드시 try-catch문으로 예외처리를 해야 한다.

8. finally블럭

 예외의 발생 여부에 상관없이 실행되어야 하는 코드를 포함시킬 목적으로 사용된다. try-catch문의 끝에 선택적으로 덧붙여 사용할 수 있고, try-catch-finally의 순서로 구성된다.

 

try {

        // 예외가 발생할 수 있는 문장들을 넣는다.

} catch (Exception1 e1) {

        // 예외처리를 위한 문장을 적는다.

} finally {

        // 예외의 발생여부에 관계없이 항상 수행되어야 하는 문장들을 넣는다.

        // finally블럭은 try-catch문의 맨 마지막에 위치해야 한다.

}

 

예제/FinallyTest3.java

public class FinallyTest3 {
    public static void main(String[] args) {
        // method1()은 static메서드이므로 인스턴스 생성없이 직접호출가능
        FinallyTest3.method1();
        System.out.println("method1()의 수행을 마치고 main메서드로 돌아왔습니다.");
    }
    
    static void method1 () {
        try {
            System.out.println("method1()이 호출되었습니다.");
            return;     // 현재 실행중인 메서드를 종료한다.
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            System.out.println("method1()의 finally블럭이 실행되었습니다.");
        }
    }
}
실행결과

method1()이 호출되었습니다.
method1()의 finally블럭이 실행되었습니다.
method1()의 수행을 마치고 main메서드로 돌아왔습니다.

 

 try블럭에서 return문이 실행되는 경우에도 finally블럭의 문장이 먼저 실행된 후, 현재 실행 중인 메서드를 종료한다.

9. 자동 자원 반환 - try-with-resources문

 try-catch문의 변형으로 try-with-resources문이 새로 추가되었다. 주로 입출력에 사용되는 클래스 중에서 사용한 후에 꼭 닫아줘야 하는 것들이 있는데 그러한 사용했던 자원(resources)을 반환하는 목적으로 사용한다.

 

 try-with-resources문의 괄호( )안에 객체를 생성하는 문장을 넣으면 이 객체는 따로 close( )를 호출하지 않아도 try블럭을 벗어나는 순간 자동적으로 close( )가 호출된다. 그리고 그다음 catch블럭 또는 finally블럭이 수행된다.

 

 이처럼 try-with-resources문에 의해 자동적으로 객체의 close( )가 호출되려면 클래스가 AutoCloseabe이란 인터페이스를 구현한 것이어야만 한다.

 

만약 호출된 close( )에서 예외가 발생하면 어떻게 될까?

 

예제/TryWithResourceEx.java

public class TryWithResourceEx {
    public static void main(String[] args) {
        try(CloseableResource cr = new CloseableResource()) {
            cr.exceptionWork(false);        // 예외가 발생하지 않는다.
        } catch (WorkException e) {
            e.printStackTrace();
        } catch (CloseException e) {
            e.printStackTrace();
        }
        System.out.println();

        try(CloseableResource cr = new CloseableResource()) {
            cr.exceptionWork(true);        // 예외가 발생한다.
        } catch (WorkException e) {
            e.printStackTrace();
        } catch (CloseException e) {
            e.printStackTrace();
        }
    }
}

class CloseableResource implements AutoCloseable {
    public void exceptionWork(boolean exception) throws WorkException {
        System.out.println("exceptionWork(" + exception + ")가 호출됨");

        if(exception)
            throw new WorkException("WorkException발생!!!");
    }

    public void close() throws CloseException {
        System.out.println("close()가 호출됨");
        throw new CloseException("CloseException발생!!!");
    }
}

class WorkException extends Exception {
    WorkException(String msg) { super(msg); }
}

class CloseException extends Exception {
    CloseException(String msg) { super(msg); }
}
실행결과

exceptionWork(false)가 호출됨
close()가 호출됨
CloseException: CloseException발생!!!
	at CloseableResource.close(TryWithResourceEx.java:32)
	at TryWithResourceEx.main(TryWithResourceEx.java:5)
    
exceptionWork(true)가 호출됨
close()가 호출됨
WorkException: WorkException발생!!!
	at CloseableResource.exceptionWork(TryWithResourceEx.java:27)
	at TryWithResourceEx.main(TryWithResourceEx.java:13)
	Suppressed: CloseException: CloseException발생!!!
		at CloseableResource.close(TryWithResourceEx.java:32)
		at TryWithResourceEx.main(TryWithResourceEx.java:14)

 

 첫 번째는 일반적인 예외가 발생했을 때와 같은 형태로 출력되었지만, 두 번째는 '억제된(supressed)'라는 의미의 머리말과 함께 출력되었다.

 

 두 예외가 동시에 발생할 수는 없기 때문에, 실제 발생한 예외를 WorkException으로 하고, CloseException은 억제된 예외로 다룬다. 억제된 예외에 대한 정보는 실제로 발생한 예외인 WorkException에 저장된다.

 

Throwable에는 억제된 예외와 관련된 다음과 같은 메서드가 정의되어 있다.

 

void addSuppressed(Throwable exception)   억제된 예외를 추가

Throwable[ ] getSuppressed( )                              억제된 예외(배열)를 반환 

 

 

 

 

 

 

출처 | Java의 정석 (남궁 성)

728x90