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의 생성자를 사용했다.
'JAVA의 정석' 카테고리의 다른 글
String 클래스 (0) | 2024.02.22 |
---|---|
Object 클래스 (0) | 2024.02.16 |
예외 처리(3) - 예외 발생시키기, 예외 떠넘기기 (0) | 2024.01.29 |
예외 처리(2) - try-catch의 흐름, 멀티 catch블럭 (1) | 2024.01.28 |
예외 처리(1) - 정의와 구조 (0) | 2024.01.28 |