기존의 정의된 예외 클래스 외에 필요에 따라 프로그래머가 새로운 예외 클래스를 정의하여 사용할 수 있다. 보통 Exception클래스 또는 RuntimeException클래스로부터 상속받아 클래스를 만들지만, 필요에 따라서 알맞은 예외 클래스를 선택할 수 있다.

가능하면 새로운 예외 클래스를 만들기보다 기존의 예외클래스를 활용하자.

class MyException extends Exception {
	MyException(String msg) {	//문자열을 매개변수로 받는 생성자.
		super(msg);				// 조상인 Exception클래스의 생성자를 호출한다.
	}
}

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

Exception클래스는 생성 시에 String값을 받아서 메세지로 저장할 수 있다. 

class MyException extends Exception {
	//에러 코드 값을 저장하기 위한 필드를 추가한다
    private final int ERR_CODE;	// 생성자를 통해 초기화 한다.
    
    MyException(String msg, int errCode) {	//생성자
    	super(msg);
    	Err_CODE = errCode;
    }
    
    MyException(String msg) {	// 생성자
    	this.(msg, 100);
    }
    
    public int getErrCode() {	// 에러 코드를 얻을 수 있는 메서드도 함께 추가했다.
    	return Err_CODE;		// 이 메서드는 주로 getMessage()와 함께 사용될 것이다.
    }
}

이전의 코드를 좀더 개선하여 메세지 뿐만 아니라 에러코드 값도 저장할 수 있도록 ERR_CODE와 getErrCode()를 MyException클래스의 멤버로 추가했다.

이렇게 함으로써 MyException이 발생했을 때, catch블럭에서 getMessage()와 getErrCode()를 사용해서 에러코드와 메세지를 모두 얻을 수 있을 것이다.

기존의 예외 클래스는 주로 Exception을 상속받아서 'checked예외'로 작성하는 경우가 많았지만, 요즘은 예외처리를 선택적으로 할 수 있도록 RuntimeException을 상속받아서 작성하는 쪽으로 바뀌어가고 있다. 'checked예외'는 반드시 예외처리를 해주어야 하기 때문에 예외처리가 불필요한 경우에도 try-catch문을 넣어서 코드가 복잡해지기 때문이다.

 

예외처리를 강제하도록 한 이유는 프로그래밍 경험이 적은 사람들도 보다 견고한 프로그램을 작성할 수 있게 유도하기 위한 것이었는데, 필수적이었던 예외들이 선택적으로 처리해도 되게 되었다. 그래서 필요에 따라 'unchecked예외'의 필요성이 증가했다.

class NewExceptionTest {
	public static void main(String[] args) {
		try {
			startInstall();
			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();
			System.out.println("다시 설치를 시도하세요.");
		} finally {
			deleteTempFiles();
		}
	}

	static void startInstall() throws SpaceException, MemoryException {
		if (!enoughSpace())
			throw new SpaceException("설치할 공간이 부족합니다.");
		if (!enoughMemory())
			throw new MemoryException("메모리가 부족합니다.");
	}

	static void copyFiles() {
		/* 파일을 복사하는 코드를 적는다 */}

	static void deleteTempFiles() {
		/* 임시파일을 삭제하는 코드를 적는다 */}

	static boolean enoughSpace() {
		// 설치하는데 필요한 공간이 있는지 확인하는 코드를 적는다.
		return false;
	}

	static boolean enoughMemory() {
		// 설치하는데 필요한 메모리공간이 있는지 확인하는 코드를 적는다.
		return true;
	}
}

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

class MemoryException extends Exception {
	MemoryException(String msg) {
		super(msg);
	}
}
에러 메세지 : 설치할 공간이 부족합니다.
a210721.SpaceException: 설치할 공간이 부족합니다.
공간을 확보한 후에 다시 설치하시기 바랍니다.
	at a210721.NewExceptionTest.startInstall(NewExceptionTest.java:24)
	at a210721.NewExceptionTest.main(NewExceptionTest.java:6)

에러문은 돌릴때마다 순서가 달라진다.

 이 두 예외는 startInstall()을 수행하는 동안에 발생할 수 있으며, enoughSpace()와 enoughMemory()의 실행결과에 따라서 발생하는 예외의 종류가 달라지도록 했다.

 

 

 

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

한 메서드에서 발생할 수 있는 예외가 여럿인 경우, 몇 개는 try-catch문을 통해서 메서드 내에서 자체적으로 처리하고, 그 나머지는 선언부에 지정하여 호출한 메서드에서 처리하도록 함으로써, 양쪽에서 나눠서 처리되도록 할 수 있다.

 그리고 심지어는 단 하나의 예외에 대해서도 예외가 발생한 메서드와 호출한 메서드, 양쪽에서 처리하도록 할 수 있다.이것은 예외를 처리한 후에 인위적으로 다시 발생시키는 방법을 통해서 가능한데, 이것을 '예외 되던지기(exception re-throwing)'라고 한다.

 먼저 예외가 발생할 가능성이 있는 메서드에 try-catch문을 사용해서 예외를 다시 처리해주고 catch문에서 필요한 작업을 행한 후에 throw문을 사용해서 예외를 다시 발생시킨다. 다시 발생한 예외는 이 메서드를 호출한 메서드에게 전달되고 호출한 메서드의 try-catch문에서 예외를 또다시 처리한다.

 이 방법은 하나의 예외에 대해서 예외가 발생한 메서드와 이를 호춣나 메서드 양쪽 모두에서 처리해줘야 할 작업이 있을 때 사용된다. 이 때 주의할 점은 예외가 발생할 메서드에서는 try-catch문을 사용해서 예외처리를 해줌과 동시에 메서드의 선언부에 발생할 예외를 throws에 지정해줘야 한다는 것이다.

public class ExceptionEx17 {
	public static void main(String[] args) {
		try {
			method1();
		} catch (Exception e) {
			e.printStackTrace();
			System.out.println("main메서드에서 예외가 처리되었습니다.");
		}
	}

	static void method1() throws Exception {
		try {
			throw new Exception();
		} catch (Exception e) {
			e.printStackTrace();
			System.out.println("method1메서드에서 예외가 처리되었습니다.");
			throw e;
		}
	}
}

결과에서 알 수 있듯이 metod1()과 main메서드 양쪽의 catch블럭이 모두 수행되었음을 알 수 있다. method1()의 catch블럭에서 예외를 처리하고도 throw문을 통해 다시 예외를 발생 시켰다. 그리고 이 예외를 main메서드 한 번 더 처리하였다.

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

static int method1() {
	try{
		System.out.println("method1()이 호출되었습니다.");
		return 0;
	} catch (Exception e)	{
		e.printStackTrace();
		return 1;
	} finally {
		System.out.println("method1()의 finally블럭이 실행되었습니다.");
	}
}

catch블럭에서 예외 되던지기를 해서 호출한 메서드로 예외를 전달하면, return문이 없어도 된다. 그래서 검증에서도 assert문 대신 AssertError를 생성해서 던진다.

assert문은 검증(assertion)을 수행하기 위한 문장이다.

static int metod1() throws Exception {
	try {
		System.out.println("method1()이 호출되었습니다.");
		return 0;
	} catch (Exception e) {
		e.printStackTrace();
		return 1;
		throw new Exception();
	} finally {
		System.out.println("method1()의 finally블럭이 실행되었습니다.");
	}
}

finally 블럭내에도 return문을 사용할 수 있으며, try블럭이나 catch블럭의 return문 다음에 수행된다. 최종적으로 finally블럭 내의 return문 값이 반환된다.

 

 

 

 

연결된 예외(chained exception)

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

try {
	startInstall();
	copyFiles();
} catch (SpaceException e) {
	InstallException ie = new InstallException("설치중 예외발생");
	ie.initCause(e);
	throw ie;
} catch(MemoryException me)	{
...

먼저 InstallException을 생성한 후에, initCause()로 SpaceException을 InstallException의 원인 예외로 등록한다. 그러고'throw'로 이 예외를 던진다.

 

initCause()는 Exception클래스의 조상인 Throwable클래스에 정의되어 있기 때문에 모든 예외에서 사용가능하다.

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

발생한 예외를 원인 예외로 등록해서 다시 예외를 발생시키는 이유는 여러 예외를 하나의 큰 분류의 예외로 묶어서 다루기 위해서이다.

 

+ Recent posts