본문 바로가기
멀티캠퍼스 풀스택 과정/Java의 정석

자바의 정석5-3 사용자 정의 예외, 예외 되던지기, 연결된 예외

by 이쟝 2022. 1. 6.

사용자 정의 예외(CustomException)

사용자가 직접 예외 클래스를 정의할 수 있다. 

- 사용자 정의 예외는 JVM에서 예외를 발생시켜 주지 않기 때문에 직접 예외를 생성해야 함

- 사용자 정의 예외 클래스 이름은 Exception으로 끝나는 것이 좋음

 

- 조상은 ExceptionRuntimeException중에서 선택함

-> Exception은 필수처리 예외라서 try-catch문을 꼭 사용해야 하기 때문에 가급적으로 선택처리인 RuntimeException을 선택!!(필요한 경우에만 필수처리로!)

 

- 사용자 정의 예외 클래스도 필드, 생성자, 메서드 선언을 포함할 수 있지만, 대부분 생성자 선언만 포함!

- 생성자는 예외 발생 원인(예외 메시지)를 전달하기 위해 String타입의 매개변수를 갖는다!

- 예외 메시지의 용도 -> catch{ } 블록의 예외처리코드에서 이용하기 위해서

 

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

 

-> Exception클래스로부터 상속받아서 MyException클래스를 생성! 필요하면 멤버변수나 메서드를 추가할 수 있음

 

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

 

예제1) 은행 예금보다 더 많은 금액을 인출하려고 하는 경우, 예외 문구를 사용자에게 출력

 

public class CustomException {

	public static void main(String[] args) {

		Account account = new Account(20000); // Account 객체 생성
		System.out.println("이 계좌의 주인은 " + account.getName() + "씨입니다."); 
		System.out.println("이 계좌의 잔액은 " + account.currentMoney + "원 입니다.");
		
		try{
			account.withdraw(19000);  // 출금하려는 가격 (withdraw 메서드 호출)
		}catch(BankingException e) {  // 예외처리!!
			System.out.println(e.getMessage());
		}
	}

}
class BankingException extends Exception {  // BankingException 클래스 정의
	BankingException() {}				    // 생성자 정의(매개변수 없는) 
	public BankingException(String msg) {   // 메시지를 넘기기 위해서 String으로 매개변수를
		super(msg); 
	}
}

class Account {  // Account 클래스 생성 
	private String name = "이지향";  // name 인스턴스 변수를 private으로 
	int currentMoney;     // 현재 금액
	
	public void setName(String name) {  // setName으로 이름 가져오기
		this.name = name;
	}
	public String getName() { // getName으로 이름 반환하기
		return name;
	}
	
	Account(int current) {  // 매개변수가 있는 Account의 생성자
		this.setName(name);
		this.currentMoney = current;
	}

	void withdraw(int money) throws BankingException {
		if(money>currentMoney)                           // 츨금 금액이 원금보다 크면
			throw new BankingException("잔액이 부족합니다."); // 예외발생!
		else											 // 출금 금액이 원금보다 작으면
			currentMoney -= money;						 // 원금에서 출금 금액 빼고
			System.out.println("현재 남은 금액: " + currentMoney + "원"); // 현재 남은 금액 출력
	}
}

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

- 예외를 처리한 후에 다시 예외를 발생시키는 (예외를 처리하고 다시 예외를 발생시킴)

- 호출한 메서드와 호출된 메서드 양쪽 모두에서 예외처리하는 것

 

예제) 호출한 main메서드와 호출 받은 method1 모두 예외처리하는 프로그램

 

public class ExceptionEx15 {
	public static void main(String[] args) {
		try {
			method1();      // method1 호출
		} catch (Exception e) {  // throw e 에서 넘어온 예외를 처리
			System.out.println("main에서 예외처리 되었습니다.");
		}
	} // main메서드의 끝
	
	static void method1() throws Exception {  //예외선언 때문에 throws Exception 추가!
		try {
			throw new Exception();  // 예외 발생
		} catch (Exception e) {
			System.out.println("method1에서 예외처리 되었습니다."); 
			throw e;    // 다시 예외를 발생시킨다. 
		}
	} // method1의 끝
} // class의 끝

 

예외 처리 방법

1) 호출 받은 쪽에서(method1) try-catch구문으로 처리하는 방법

2) 호출한 쪽에서(main) try-catch구문으로 처리하는 방법

3) 양쪽에서 try-catch구문으로 처리하는 방법


연결된 예외(chained exception)

- 한 예외가 다른 예외를 발생시킬 수 있다.

- 예외 A예외 B발생시키면, AB원인 예외(cause exception)

- 연결된예외: 어떤 예외를 다른 예외로 감싸는 것(세부적인 예외들을 포괄적인 예외들로 감싼다)

 

A는 B의 원인예외

 

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

 

-> Throwable cause에 원인 예외 A를 넣음

-> initCause메서드는 Exception클래스와 RuntimeException의 조상인 ThrowAble클래스에 정의되어 있어서 모든 예외에서 사용가능

 

예제) SpaceException(A)을 원인 예외로 하는 InstallException(B)을 발생시키는 방법

 

void install() throws InstallException {
	try {
		starInstall();    // SpaceException 발생(저장공간부족)
		copyFiles();
	} catch (SpaceException e ) { 
		InstallException ie = new InstallException("설치중 예외발생") // 예외생성
		ie.initCasue(e)  // InstallException의 원인예외를 SpaceExceptionm으로 지정
		throw.ie;        // InstallException을 발생시킨다. 
	} catch (MemoryException me) {
		...
}	...

 

더보기
더보기

- SpaceException e -> 예외 A, InstallException -> 예외 B

- initCause 메서드를 사용해서 AB에 포함!

-> 먼저 InstallException을 생성(1)한 후에, initCause()SpaceException(A)InstallException(B)의 원인예외로 등록(2)하고 throw로 이 예외(InstallException)를 던진다(3)!!!!

-> 생성된 예외가 InstallException이어서 메서드 선언부에 SpaceException이 아닌 InstallException이 옴!

 

(1) InstallException ie = new InstallException( );

(2) ie.initCause(e)

(3) throw.ie;


[연결된 예외를 사용하는 이유1] 여러 예외를 하나의 큰 분류의 예외로 묶어서 다루기 위해서

 

 

- 프로그램을 설치하다가 SpaceException 예외가 발생되어서 결과 -> 설치할 공간이 부족합니다라는 에러메세지가 뜸!

- 하지만 설치하다가 발생할 수 있는 에러가 더 생길 수도 있음!(MemoryException이나 SpaceException 말고도..!

-> 그러면 install이라는 메서드를 쓸 때 마다 Catch 블록이 너무 많이 사용하게 됨

-> 그럴 때 연결된 예외를 사용해서 많은 에러를 => InstallException 하나로 줄여서 다룰 수 있음!

 

 

-> 이 코드처럼 install( )메서드 안으로 Catch블록들(발생할 예외들)을 집어넣으면 됨

-> 그리고 startInstall( ); 이 시작되면서 예외가 발생하게 되면 그 예외를 InstallException안에 넣어주게 됨

-> 세부적인 예외처리는 install( )메서드 안에 감출 수 있고 InstallException만 던지도록 선언!(그래서 throws 옆에 InstallExcepton) 

-> install( )내부에서는 예외처리를 한 게 아니라 두 예외를 연결하는 작업을 함

 

 

결과

- install( ); 메서드를 호출했을 때 SpaceException이나 MemoryException이 아니라 InstallException이 발생하게 됨!

- install( ) 선언부의 InstallExceptioncatch 블록에 넣으면 됨! catch(InstallException e)

- 결과: 발생한 예외인 InstallException으로 대략정보(설치 중 예외발생)와 원인 예외(실제로 발생한 예외) 인 SpaceException으로 세부정보(설치할 공간이 부족합니다)를 알 수 있다!


[이유2] checked예외를 unchecked예외로 변경하려 할 때(필수처리 -> 선택처리)

checked예외: Exception클래스 자손, 필수처리 / unchecked예외: RuntimeException클래스, 선택처리

- 필수예외를 선택예외로 바꾸는 이유? 필수처리예외는 try-catch문을 꼭 써야하기 때문에 제약이 생김

 

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

 

-> spaceException과 MemoryException 둘다 필수예외처리!

-> 만약 MemoryException을 선택처리로 바꾸고 싶다면 위 코드를 아래코드로 변경

 

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

 

더보기
더보기

-> MemoryException Exception의 자손이므로 반드시 예외처리를 해야하는데 이것을 RuntimeException으로 감싸버려서 unchecked예외(선택처리)가 되었다. 그래서 startInstall( )선언부에 MemoryException을 선언하지 않아도 된다.!

-> 원래 선언부에 SpaceMemory가 있었지만 Memory가 필수에서 선택으로 바뀌게 되면서 선언부에 Memory를 생략!

-> RuntimeException을 만들고 안에다가 집어넣으면 됨! 필수예외를 선택예외로 감싸는 것

-> RuntimeException으로 감싸면서 RuntimeException인 것처럼 위장하기!

-> MemoryException을 원인 예외로 등록!(앞예제에서는 initCause로 원인예외로 등록했고 지금은 생성자로 원인예외로 등록!)