본문 바로가기

JAVA의 정석

예외 처리(4) - 사용자 정의 예외 만들기, 연결된 예외

 

1. 사용자 정의 예외 만들기

기존의 정의된 예외 클래스 외에 필요에 따라 프로그래머가 새로운 예외 클래스를 정의하여 사용할 수 있다.

 

보통 Exception클래스 또는 RuntimeException클래스로부터 상속받는 클래스를 만들지만, 필요에 따라서 알맞은 예외를 선택할 수 있다.

 

 

RuntimeException 클래스로부터 상속 받아서 MyException 클래스를 만들었다. 필요하다면, 멤버변수나 메서드를 추가할 수 있다.

 

RuntimeException 클래스는 생성 시에 String 값을 받아서 메시지로 저장할 수 있다. 직접 만든 사용자정의 예외 클래스도

 

메세지를 저장하게 만드려면 위 예제처럼 String을 매개변수로 받는 생성자를 추가해주어야 한다.

 

 

 

이전의 코드를 좀더 개선하여 메시지뿐만 아니라 에러코드 값도 저장할 수 있도록 하였다. 이렇게 함으로써 MyException이 발생했을 때,

 

catch 블럭에서 getMessage()와 getErrorCode()를 사용해서 에러코드와 메세지를 모두 얻을 수 있다. 

 

기존의 예외 클래스는 주로 Exception을 상속 받아서 'checked 예외'로 작성하는 경우가 많았지만, 요즘은 예외처리를 선택적으로

 

할 수 있도록 RuntimeException을 상속받아서 작성하는 쪽으로 바뀌어가고 있다. 'checked예외'는 반드시 예외처리를 해주어야 하기 때

 

문에 예외처리가 불필요한 경우에도 try-catch문을 넣어서 코드가 복잡해지기 때문이다.

 

 

public class Ex8_11 {
    public static void main(String args[]){
        try{
            strartInstall();    // 프로그램 설치에 필요한 준비를 한다.
            copyFiles();        // 파일들을 복사한다.
        }   catch (SpaceException e) {
            System.out.println("에러 메세지 : " + e.getMessage());
            e.printStackTrace();
            System.out.println("공간을 확보한 후에 다시 설치하시기 바랍니다.");
        } catch (MemoryException me) {
            System.out.println("에러 메세지 : " + me.getMessage());
            me.printStackTrace();
            System.gc();        // Garvage Collection을 수행하여 메모리를 늘려준다.
            System.out.println("다시 설치를 시도하세요.");
        }   finally {
            deleteTempFiles();  // 프로그램 설치에 사용된 임시파일들을 삭제한다.
        } // try의 끝
    } // main의 끝

    static void strartInstall() throws SpaceException, MemoryException {
        if(!enoughSpace())      //  충분한 설치 공간이 없으면
            throw new SpaceException("설치할 공간이 부족합니다.");
        if(!enoughMemory())     //  충분한 설치 메모리가 없으면
            throw new MemoryException("설치할 공간이 부족합니다.");
    } // startInstall 메서드의 끝

    static void copyFiles(){ /* 파일들을 복사하는 코드를 적는다.*/ }
    static void deleteTempFiles(){ /* 임시파일들을 삭제하는 코드를 적는다.*/ }
    static boolean enoughSpace() {return false;}// 설치하는데 필요한 공간이 있는지 확인하는 코드를 적는다.
    static boolean enoughMemory() {return true;}// 설치하는데 필요한 메모리공간이 있는지 확인하는 코드를 적는다.

} //ExceptionTest 클래스의 끝

class SpaceException extends Exception {
    SpaceException(String msg){
        super(msg);
    }
}
class MemoryException extends Exception {
    MemoryException(String msg){
        super(msg);
    }
}




 

 

실행 결과 :

 

 

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

 

한 메서드에서 발생할 수 있는 예외가 여럿인 경우, 몇 개는 try-catch문을 통해서 메서드 내에서 자체적으로 처리하고, 나머지는

 

선언부에 지정하여 호출한 메서드에서 처리하도록 함으로써 양쪽에서 나눠서 처리하도록 할 수 있다.

 

그리고 심지어는 단하나의 예외에 대해서도 예외가 발생한 메서드와 호출한 메서드, 양쪽에서 처리할 수 있도록 있다.

 

이것은 예외를 처리한 후에 인위적으로 다시 발생시키는 방법을 통해서 가능한데 이것을 '예외 되던지기(exception re-throwing)'라고

 

한다. 먼저 예외가 발생할 가능성이 있는 메서드에서 try-catch 문을 사용해서 예외를 처리해주고 catch문에서 필요한 작업을 행한 후에

 

throw문을 사용해서 예외를 다시 발생시킨다. 다시 발생한 예외는 이 메서드를 호출한 메서드에게 전달되고 호출한 메서드의 try-catch문

 

에서 예외를 또다시 처리한다.

 

이 방법은 하나의 예외에 대해서 발생한 예외가 발생한 메서드와 이를 호출한 메서드 양쪽 모두 에서 처리해줘야 할 작업이 있을 때

 

사용된다. 이 때 주의할 점은 예외가 발생할 메서드에서는 try-catch문을 사용해서 예외처리를 해줌과 동시에 메서드의 선언부에 발생할 예

 

외를 throw에 지정해줘야한다는 것이다.

 

 

 

결과에서 알 수 있듯이 method1()과 main메서드 양쪽의 catch블럭이 모두 수행되었음을 알 수 있다. method1()의 catch 블럭에서

 

예외를 처리하고도 throw문을 통해 다시 예외를 발생 시켰다. 그리고 이 예외를 main메서드에서 한 번 더 처리한 것이다. 

 

반환값이 있는 return문의 경우, catch블럭에도 return문이 있어야 한다. 예외가 발생했을 경우에도 값을 반환해야하기 때문이다.

 

static int method1 () {
     try {
             System.out.println("method1()이 호출되었습니다.")
             return 0;  // 현재 실행 중인 메서드를 종료한다.
}    catch (Exception) 

 

3. 연결된 예외(chained exception)

 

한 예외가 다른 예외를 발생시킬 수도 있다. 예를 들어 예외 A가 예외 B를 발생시켰다면, A를 B의 '원인 예외 (cause exception)'이라

 

한다. 아래의 코드는 예제 8-11의 일부를 변경한 것으로, SpaceException을 원인 예외로 하는 InstallException을 발생시키는 방법을

 

보여준다. 

 

try{
    startInstall();
    copyFiles();
}   catch(SpaceException e) {
    InstallException ie = New InstallException("설치중 예외발생"); // 예외 생성
    ie.initCause(e); // InstallException의 원인 예외를 SpaceException으로 지정
    throw ie;        // InstallException을 발생시킨다.
}   catch (MemoryException me) {
    ...
}

 

 

먼저 InstallException을 생성한 후에, initCause()로 SpaceException을 InstallException의 원인 예외로 등록한다. 

 

그리고 'throw'로 이 예외를 던진다. initCause()는 Exception클래스의 조상인 Throwable 클래스에 정의되어 있기 때문에

 

모든 예외에서 사용 가능하다.

 

Throwable initCause(Throwable cause) : 지정한 예외를 원인 예외로 등록
Throwable getCause() : 원인 예외를 반환

 

 

발생한 예외를 그냥 처리하면 될텐데, 원인 예외로 등록해서 다시 예외를 발생시키는지 궁금할 것이다. 그 이유는 여러가지 예외를

 

하나의 큰 분류의 예외로 묶어서 다루기 위해서이다. 예를 들어 InstallException을 SpaceException과 MemoryException의 부모

 

클래스로 두어 상속관계를 두는 것보다 원인 예외를 포함하는 것이 부담이 덜 되기 때문이다.

 

또 다른 이유는 checked 예외를 unchecked 예외로 바꿀 수 있도록 하기 위해서이다.

 

checked 예외로 예외처리를 강제한 이유는 프로그래밍 경험이 적은 사람도 보다 견고한 프로그램을 작성할 수 있도록 유도하기

 

위한 것이었는데, 지금은 자바가 처음 개발되던 1990년대와 컴퓨터 환경이 많이 달라졌다. 그래서 checked예외가 발생해도

 

예외를 처리할 수 없는 상황이 하나 둘 발생하기 시작했다. 이럴 떄 할 수 있는 일이라곤 그저 의미 없는 try-catch문을 추가하는 것

 

뿐인데, checked 예외를 unchecked 예외로 바꾸면 예외처리가 선택적이 되므로 억지로 예외처리를 하지 않아도 된다.

 

 

 

static void startInstall() throws SpaceException, MemoryException{
    if(!enoughSpace())              // 충분한 설치 공간이 없으면 ..
        throw new SpaceException("설치할 공간이 부족합니다.");

    if(!enoughMemory())             // 충분한 메모리 공간이 없으면 ..
        throw new MemoryException("메모리가 부족합니다.");
}

 

Before


 

static void startInstall() throws SpaceException,{
    if(!enoughSpace())              // 충분한 설치 공간이 없으면 ..
        throw new SpaceException("설치할 공간이 부족합니다.");

    if(!enoughMemory())             // 충분한 메모리 공간이 없으면 ..
        throw new RuntimeException(new MemoryException());
}

 

After

 

 

 

MemoryException은 Exception의 자손이므로 반드시 예외를 처리해야 하는데, 이 예외를 RuntimeException으로 감싸버렸기

 

때문에 unchecked 예외가 되었다. 그래서 더 이상 startInstall()의 선언부에 MemoryException을 선언하지 않아도 된다.

 

참고로 위의 코드에서는 initCause()대신 RuntimeException의 생성자를 사용했다.