공부했던 자료 정리하는 용도입니다.

재배포, 수정하지 마세요.

 

 

 

 

예외처리(Exception Handling)

컴파일 에러(compile-time error) : 컴파일시에 발생하는 에러
런타임 에러(runtime error) : 실행시에 발생하는 에러
논리적에러(logical error) : 실행은 되지만, 의도와 다르게 동작하는것

 프로그램 실행 중 어떤 원인에 의해서 오작동하거나 비정상적으로 종료되는 경우가 있다. 이러한 결과를 초래하는 원인을 프로그램 에러 또는 오류라고 한다. 발생 시점에 따라 위의 표처럼 3가지로 구분된다. 자바에서 소스코드를 컴파일하면 컴파일러가 소스코드( *.java )의 오타나 잘못된 구문, 자료형 체크 등의 기본적인 검사를 수행하여 오류가 있는지 알려준다. 만약 오류가 없으면 컴파일을 성공하고 클래스 파일( *.class )이 생성된다.

 

 

에러(error) : 프로그램 코드에 의해서 수습될 수 없는 심각한 오류
예외(exception) : 프로그램 코드에 의해서 수습될 수 있는 다소 미약한 오류

 그러나 컴파일에는 문제가 없더라도 실행 중에 에러가 발생해서 잘못된 결과를 얻거나 프로그램이 비정상적으로 종료될 수 있다. 런타임 에러를 방지하기 위해서는 프로그램의 실행 도중 발생할 수 있는 경우의 수를 고려하여 이에 대한 대비를 를 해야 한다. 자바에서는 실행 시(runtime) 발생할 수 있는 오류를 '에러(error)'와 '예외(exception)', 2가지로 구분하였다. 에러는 메모리 부족( OutOfMemoryError )이나 스택오버플로우( StackOverflowError )와 같이 일단 발생하면 복구할 수 없는 심각한 오류이고, 예외는 발생하더라도 수습될 수 있는 비교적 덜 심각한 것들이다. 에러가 발생하면 프로그램의 비정상적인 종료를 막을 길이 없지만, 예외는 발생하더라도 이에 대한 대응 코드를 미리 작성해 놓음으로써 프로그램의 비정상적인 종료를 막을 수 있다. 

 

 

 

 

예외 클래스의 계층구조

예외클래스 계층도

자바에서는 실행 시 발생할 수 있는 오류( Exception  Error )를 클래스로 정의하였다.  Object 는 모든 클래스의 부모 클래스이기 때문에  Exception  Error 클래스 역시  Object 클래스의 자식들이다.

 

 

Exception클래스와 RuntimeException클래스 중심의 상속계층도

 


 Exception클래스들(흰색부분) : 사용자의 실수와 같은 외적인 요인에 의해 발생하는 예외 

  FileNotFoundException : 존재하지 않는 파일의 이름을 입력한 경우 발생
  ClassNotFoundException : 클래스의 이름을 잘못 적었을 경우 발생
DataFormatException : 입력한 데이터 형식이 잘못된 경우 발생 

 RuntimeException클래스들(파란색 부분) : 프로그래머의 실수로 발생하는 예외 
ArrayIndexOutOfBoundsException : 배열의 범위를 벗어난 경우 발생
NullPointerException : 값이  null 인 참조변수의 멤버를 호출하려 하는 경우 발생
ClassCastException : 클래스의 형변환을 잘못한 경우 발생
ArithmeticException : 정수를  0 으로 나누려고 하는 경우 발생

모든 예외의 최고 조상은  Exception 클래스이다.  RuntimeException 클래스와 그의 자식 클래스들(파란색 부분)은 주로 프로그래머의 실수에 의해서 발생될 수 있는 예외들이고 그 외의  Exception 클래스들(흰색 부분)은 주로 외부의 영향으로 발생할 수 있는 것들로서, 프로그램을 이용하는 사용자들의 동작에 의해서 발생하는 경우가 많다. 

 

 

 

 

try - catch문

예외 처리 : 프로그램 실행 시 발생할 수 있는 예외에 대비한 코드를 작성하는 것
예외처리 목적 : 프로그램의 비정상종료를 막고, 정상적인 실행상태를 유지하는것

프로그램의 실행 도중 발생하는 에러는 어쩔 수 없지만 예외는 프로그래머가 이에 대한 처리를 미리 해주어야 한다. 발생한 예외를 처리하지 못하면 프로그램은 비정상적으로 종료되며, 처리되지 못한 예외(uncaught exception)는 JVM의 예외 처리기(UncaughtExceptionHandler)가 받아서 예외의 원인을 화면에 출력한다.

 

try{
    //예외가 발생할 가능성이 있는 문장
}catch(Exception1 e1){
    //Exception1이 발생했을 경우, 이를 처리하기위한 문장
}catch(Exception2 e2)
    //Exception2가 발생했을 경우, 이를 처리하기위한 문장

예외를 처리하기 위해서는  try - catch 문을 사용한다. 하나의  try 블럭 다음에는 여러 종류의 예외를 처리할 수 있도록 하나 이상의  catch 블럭이 올 수 있으며, 이중 발생한 예외의 종류와 일치하는 단 하나의  catch 블럭만 수행된다. 발생한 예외의 종류와 일치하는  catch 블럭이 없으면 예외는 처리되지 않는다. 또한 하나의 메서드 내에 여러 개의  try - catch 문이 사용될 수 있으며,  try 블럭이나  catch 블럭에 또 다른  try - catch 문이 포함될 수 있다.  catch 블럭의 괄호 내에 선언된 변수는  catch 블럭 내에서만 유효하기 때문에  catch 블럭마다 참조 변수를 같게 써도 되지만  catch 블럭내에 또 다른  try - catch 문이 포함된 경우에는 같은 이름의 참조 변수를 사용하면 안 된다. catch 블럭에 선언된 두 참조 변수의 영역이 서로 겹쳐서 이름이 달라야만 구별되기 때문이다.

 if 문과 달리  try 블럭이나  catch 블럭 내에 포함된 문장이 하나뿐이어도 괄호 { } 를 생략할 수 없다.

 

 

package test;

class test{
	public static void main(String[] args) {
		int number = 100;
		int result = 0;
		
		for(int i = 0 ; i < 10 ; i++) {
			try {
				result = number / (int)(Math.random() * 10); // 0 <= random < 10 범위의 수가 나눠짐
				System.out.println(result);
			}catch(ArithmeticException e) {	// 0으로 나눠지는 경우(예외발생)
				System.out.println("0");
			}
		}
	}
}

실행결과

 number  0 <= random < 10 범위의 랜덤한 정수 값으로 반복해서 나누는 예제이다. 원래라면  0 으로 나눌 때 프로그램이 비정상적으로 종료되어야 하는데  try - catch 문으로 예외를 처리해 주었기 때문에( 0 이 출력되도록 함) 프로그램이 정상적으로 종료되었다. 만일 예외처리를 하지 않았다면,  0 으로 나눠지는 순간 예외가 발생해서 프로그램이 비정상적으로 종료되었을 것이다. 

 

 

 

 

try-catch문에서의 흐름


    ■ try블럭 내에서 예외가 발생한 경우.

  1. 발생한 예외와 일치하는  catch 블럭이 있는지 확인한다.
  2. 일치하는  catch 블럭을 찾게되면, 그  catch 블럭 내의 문장들을 수행하고 전체  try - catch 문을 빠져나간다. 만약 일치하는  catch 블럭이 없다면 예외는 처리되지 못한다.
    ■ try블럭 내에서 예외가 발생하지 않은 경우 
  1.  catch 블럭을 거치지 않고 전체  try - catch 문을 빠져나간다.

 if 문처럼  try - catch 문에서도 예외가 발생한 경우와 발생하지 않았을 때의 흐름이 달라진다.  try 블럭에서 예외가 발생하면 예외가 발생한 위치 이후에 있는  try 블럭의 문장들은 수행되지 않으므로  try 블럭에 포함시킬 코드의 범위를 잘 선택해야 한다.

 

 

 

 

예외의 발생과 catch블럭

   catch 블럭은 괄호 ( ) 와 블럭 { }  두 부분으로 나눠져 있는데, 괄호 ( ) 내에는 처리하고자 하는 예외와 같은 타입의 참조 변수 하나를 선언해야 한다. 예외가 발생하면, 발생한 예외에 해당하는 클래스의 인스턴스가 만들어진다. 예외가 발생한 문장이  try 블럭에 포함되어 있다면, 이 예외를 처리할 수 있는  catch 블럭이 있는지 찾게 된다. 첫 번째  catch 블럭부터 차례로 내려가면서  catch 블럭의 괄호 ( ) 내에 선언된 참조 변수의 종류와 생성된 예외 클래스의 인스턴스에  instanceof 연산자를 이용해서 검사하게 되는데, 검사 결과가  true  catch 블럭을 만날 때까지 검사는 계속된다. 검사 결과가  true   catch 블럭을 찾게 되면 예외처리(블럭에 있는 문장들을 모두 수행)한 뒤에  try - catch 문을 빠져나가지만, 검사 결과가  true   catch 블럭이 하나도 없으면 예외는 처리되지 않는다.

(모든 예외 클래스는  Exception 클래스의 자식이므로,  catch 블럭의 괄호 ( )  Exception 클래스 타입의 참조 변수를 선언해 놓으면 어떤 종류의 예외가 발생하더라도  catch 블럭에서 처리할 수 있다. 예를 들어  try - catch 문의 마지막에 놓는다면 무조건 예외처리를 하게 되는 셈이다.)

 

 

  ■ printStackTrace( )와 getMessage( )

printStackTrace( ) : 예외발생 당시의 호출스택(Call Stack)에 있었던 메서드의 정보와 예외 메시지를 화면에 출력
getMessage( ) : 발생한 예외클래스의 인스턴스에 저장된 메시지를 얻을 수 있다.

예외가 발생했을 때 생성되는 예외 클래스의 인스턴스에는 발생한 예외에 대한 정보가 담겨 있다. 자주 사용되는 위의 두 함수를 통해서 정보들을 얻을 수 있으며  catch 블럭의 괄호 ( ) 에 선언된 참조 변수를 통해서 인스턴스에 접근할 수 있다. 참조 변수는 선언된  catch 블럭 내에서만 사용 가능하다. 

 

 

  ■ 멀티 catch블럭

...

try{
    ...
}catch (ExceptionA | ExceptionB e){	//여러개의 catch블럭을 |로 합칠 수 있다.
    e.printStackTrace();
}

  JDK 1.7부터 여러  catch 블럭을  | 기호(논리 연산자가 X) 통해서 하나의  catch 블럭으로 합칠 수 있게 되었고, 이를  멀티 catch 블럭 이라고 한다. 중복된 코드를 줄일 수 있으며 연결할 수 있는 예외 클래스의 개수에는 제한이 없다. 하지만  | 기호로 연결된 예외 클래스가 부모와 자식 관계에 있다면 컴파일 에러가 발생한다.(부모 클래스 하나만 써주는 것과 같기 때문에 중복을 제거하라는 의미에서 에러 발생) 그리고 멀티  catch 는 여러 개의 예외를 처리하는 것이기 때문에 참조 변수로 멤버를 사용할 때  | 기호로 연결된 예외 클래스들의 공통인 부모 예외 클래스에 선언된 멤버들만 사용 가능하다.

멀티  catch 블럭에 선언된 참조 변수  e 는 상수라서 값을 변경할 수 없다.(여러  catch 블럭이 하나의 참조 변수를 공유하기 때문)

 

 

 

 

예외 발생시키기


1.  new 연산자를 이용해서 발생시키려는 예외 클래스의 객체를 만든다.

  Exception e = new Exception("고의로 발생시킨 예외");

2.  throw 키워드를 이용해서 예외를 발생시킨다.
  throw e;

 throw 키워드를 이용해서 고의로 예외를 발생시킬 수 있다.  Exception 인스턴스를 생성할 때 생성자에 String을 넣어주면 이 String Exception 인스턴스에 메시지로 저장된다. 메시지 getMessage( ) 로 얻을 수 있다.

 

 

+  RuntimeException 클래스와 그 자식 클래스들에 해당하는 예외는 프로그래머에 의해 실수로 발생하는 것들이기 때문에 예외처리를 강제하지 않는다.(실행 중 오류가 발생하더라도 일단 컴파일은 된다. 다른 예외들은 컴파일조차 안됨) 컴파일러가 예외처리를 확인하지 않는  RuntimeException 클래스들은 'unchecked예외'라고 부르고, 예외처리를 확인하는  Exception 클래스들은 'checked예외'라고 부른다. 

 

 

 

 

메서드에 예외 선언하기

void method() throws Exception1, Exception2, ... ExceptionN {	
    //메서드의 내용
}

예외 처리할 때  try - catch 문을 사용하는 것 외에도 예외를 메서드에 선언하는 방법이 있다. 메서드에 예외를 선언하려면, 메서드의 선언부에 키워드  throws 를 사용해서 메서드 내에서 발생할 수 있는 예외를 적어주면 된다. 예외가 여러 개일 경우에는 쉼표( , )로 구분한다.  

예외를 발생시키는 키워드는  throw 고 예외를 메서드에 선언하는 키워드는  throws 이다. 헷갈리지 말기!

 

 

만약 모든 예외의 최고조상인  Exception 클래스를 메서드에 선언하면, 이 메서드는 모든 종류의 예외가 발생할 가능성이 있다는 의미이다. 하지만 이렇게 예외를 선언하면 이 예외뿐만 아니라 그 자식 타입의 예외까지도 발생할 수 있다. 오버 라이딩할 때는 상속관계까지 고려해야 하기 때문에 주의해야 한다. 메서드의 선언부에 예외를 선언하면 메서드를 사용하는 쪽에 이에 대한 처리를 하도록 강요할 수 있고, 선언부만 보고도 어떤 예외들이 처리되어야 하는지 쉽게 알 수 있어서 편리하다. 

 

 

사실 예외를 메서드의  throws 에 명시하는 것은 예외를 처리하는 것이 아니라, 자신(예외가 발생할 가능성이 있는 메서드)을 호출한 메서드에게 예외를 전달하여 예외처리를 떠맡기는 것이다. 예외를 전달받은 메서드가 또다시 자신을 호출한 메서드에게 전달할 수 있으며, 이런 식으로 계속 호출 스택에 있는 메서드들을 따라 전달되다가 제일 마지막에 있는  main 메서드에서도 예외가 처리되지 않으면  main 메서드마저 종료되어 프로그램 전체가 종료된다.

 

 

따라서 예외가 발생한 메서드 내에서 자체적으로 처리해도 되는 것은 메서드 내에서  try - catch 문을 이용해서 처리하고, 메서드 내에서 자체적으로 해결이 안 되는 경우에는 메서드에 예외를 선언해서 호출한 메서드에서 처리하도록 해야 한다.

 

 

 

 

finally블럭

try{
    //예외가 발생할 가능성이 있는 문장
}catch(Exception1 e1){
    //예외처리를 위한 문장
}finally{	//finally블럭은 try-catch문의 맨 마지막에 위치해야 한다.
    //예외의 발생여부에 관계없이 항상 수행되어야 하는 문장들
}

 finally 블럭은  try - catch 문과 함께 예외의 발생 여부에 상관없이 실행되어야 할 코드를 포함시킬 목적으로 사용된다.  try - catch 문의 끝에 선택적으로 덧붙여 사용할 수 있으며  try - catch - finally 의 순서로 구성된다. 예외가 발생한 경우에는  try  →  catch    finally 의 순서로 실행되고, 예외가 발생하지 않는 경우에는  try    finally 의 순으로 실행된다.( try 블럭이나  catch 블럭에서  return 문을 만나도  finally 블럭의 문장들은 수행된다.)

 

 

 

 

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

  JDK1.7부터  try - with - resources 문이라는  try - catch 문의 변형이 새로 추가되었다. 이 구문은 주로 입출력(I/O)과 관련된 클래스를 사용할 때 유용하다. 입출력에 사용되는 클래스중에서는 사용한 후에 꼭 닫아줘야 하는 것들이 있다. 사용했던 자원(resources)을 반환시키기 위해서이다.

 

 

try{
    fis = new FileInputStream("score.dat");
    dis = new DataInputStream(fis);
}catch(IOException ie){
    ie.printStackTrace();
}finally{
    dis.close();	//작업중에 예외가 발생하더라도, dis가 닫히도록 finally블럭에 넣음
}			//근데 close()가 예외를 발생시키면 문제가 됨



//close()에서 발생하는 예외를 처리하기 위해서 아래와같이 바꿀수도 있지만 코드가 복잡해져서 좋지않다..
try{
    fis = new FileInputStream("score.dat");
    dis = new DataInputStream(fis);
}catch(IOException ie){
    ie.printStackTrace();
}finally{
    try{
        if(dis!=null)
            dis.close();
    }catch(IOException ie){
    	ie.printStackTrace();
    }
}		

위의 코드는  DataInputStream 을 사용해서 파일로부터 데이터를 읽는 코드인데, 데이터를 읽는 도중에 예외가 발생하더라도  DataInputStream 이 닫히도록  finally 블럭 안에  close( ) 를 넣었다. 하지만  close( ) 가 예외를 발생시켜버리면 문제가 발생한다.  finally 블럭안에  try - catch 문을 추가해서  close( ) 에서 발생할 수 있는 예외를 처리하도록 해도 되지만 코드가 복잡해져서 보기에 좋지 않다. 더 나쁜 것은  try 블럭과  finally 블럭에서 모두 예외가 발생하면,  try 블럭의 예외는 무시된다는 것이다. 이러한 점을 개선하기 위해서  try - with - resources 문이 추가되었다.

 

 

//괄호()안에 두문장 이상 넣을 경우 ';'로 구분한다.
try(FileInputStream fis = new FileInputStream("score.dat");
    DataInputStream dis = new DataInputStream(fis)) {
	
    while(true){
    	score - dis.readInt();
        System.out.println(score);
        sum += score;
    }
}catch (EOFException e){
    System.out.println("점수의 총합은 " + sum + "입니다.");
}catch (IOException ie){
    ie.printStackTrace();
}

 try - with - resources 문의 괄호 ( ) 안에 객체를 생성하는 문장을 넣으면, 이 객체는 따로  close( ) 를 호출하지 않아도  try 블럭을 벗어나는 순간 자동적으로  close( ) 가 호출된다. 그다음에  catch 블럭 또는  finally 블럭이 수행된다. ( try 블럭의 괄호 ( ) 안에 변수를 선언하는 것도 가능하며, 선언된 변수는  try 블럭 내에서만 사용할 수 있다.)

 

 

public interface AutoCloseable{
    void close() throws Exception;
}

 try - with - resources 문에 자동으로 객체의  close( ) 가 호출될 수 있으려면, 클래스가  AutoCloseable 이라는 인터페이스를 구현한 것이여만 한다. 그런데 위의 코드를 보면  close( )  Exception 을 발생시킬 수 있는 것을 확인할 수 있다. 만일 자동 호출된  close( ) 에서 예외가 발생하면 어떻게 될까?

 

package Example;

public class Example {
	public static void main(String[] args) {
		try(CloseableResource cr = new CloseableResource()){
		    cr.exceptionWork(false);	//예외가 발생하지 X
		}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); }
}

 

실행결과

위의 코드를 보면  main 메서드에 2개의  try - catch 문이 있다. 첫 번째 것은  close( ) 에서만 예외를 발생시키고, 두 번째 것은  exceptionWork( )  close( ) 에서 모두 예외를 발생시킨다. 첫 번째는 일반적인 예외가 발생했을 때와 같은 형태로 출력되었지만 두 번째는 좀 다르다.  exceptionWork( ) 에서 발생한 예외에 대한 내용이 출력되고,  close( ) 에서 발생한 예외는 '억제된(suppressed)'이라는 머리말과 함께 출력되었다. 두 예외가 동시에 발생할 수는 없기 때문에 실제 발생한 예외를  WorkException 으로 하고,  CloseException 은 억제된 예외로 다룬다. 억제된 예외에 대한 정보는 실제로 발생한 예외인  WorkException 에 저장된다.  Throwable 에는 아래의 표처럼 억제된 예외와 관련된 메서드가 정의되어 있다.  

 

void addSuppressed(Throwable exception) : 억제된 예외를 추가
Throwable[ ] getSuppressed( ) : 억제된 예외(배열)을 반환

 

 

 

 

사용자 정의 예외 만들기

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

 

class MyException extends Exception{
    //에러코드 값을 저장하기 위한 필드
    private final int ERR_CODE;	//생성자를 통해서 초기화됨
    
    MyException(String msg, int errCode){	//문자열과 에러코드를 매개변수로 받는 생성자
    	super(msg)	//부모인 Exception클래스의 생성자를 호출한다.
        ERR_CODE = errCode;
    }
    
    MyException(String msg){ //문자열을 매개변수로 받는 생성자 (Exception클래스는 생성시에 String값을 받아서 메시지로 저장할 수 있다.)
    	this(msg, 100);	//ERR_CODE를 100(기본값)으로 초기화한다.
    }
    
    public int getErrCode(){	//에러코드를 얻을 수 있도록 메서드를 선언
    	return ERR_CODE;    //이 메서드는 주로 getMessage()와 함께 사용될 것이다.
    }
}

위의 코드처럼 사용자 정의 예외를 만들면  MyException 이 발생했을 때  catch 블럭에서  getMessage( )  getErrCode( ) 를 이용해서 에러코드와 메시지를 모두 얻을 수 있다.

 

 

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

 

 

 

 

예외 되던지기(Exception Re-throwing)

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

  이는 예외를 처리한 후에 인위적으로 다시 발생시키는 방법을 통해서 가능한데, 이것을 '예외 되던지기(exception re-throwing)'라고 한다. 먼저 예외가 발생할 가능성이 있는 메서드에서  try - catch 문을 사용해서 예외를 처리해주고  catch 문에서 필요한 작업을 한 뒤  throw 문을 사용해서 예외를 다시 발생시킨다. 다시 발생한 예외는 해당 메서드를 호출한 메서드에게 전달되고 호출한 메서드의  try - catch 문에서 또다시 예외를 처리한다. 이 방법은 하나의 예외에 대해서 예외가 발생한 메서드와 이를 호출한 메서드 양쪽 모두에서 처리해줘야 할 작업이 있을 때 사용된다. 이때 예외가 발생할 메서드에서는  try - catch 문을 사용해서 예외처리를 해줌과 동시에 메서드의 선언부에 발생할 예외를  throws 로 지정해주어야 한다. 

 

 

package Example;

public class Example {
	public static void main(String[] args) {
		try {
		    method1();
		}catch(Exception e) {
		    System.out.println("main메서드에서 예외가 처리됨.");
		}
	}
	
	static void method1() throws Exception{
		try {
		    throw new Exception();
		}catch(Exception e) {
		    System.out.println("method1에서 예외가 처리됨");
		    throw e;	//다시 예외를 발생시킨다.
		}
	}
}

 

실행결과

위의 코드를 보면  method1( )  catch 블럭에서 예외를 처리하고  throw 문을 통해서 다시 예외를 발생시켰다. 그리고 이 예외를  main 에서 또 한 번 처리하였다. 반환 값이 있는  return 문의 경우,  catch 블럭에도  return 문이 있어야 한다. 예외가 발생한 경우에도 값을 반환해야 하기 때문이다. 또는  catch 블럭에서 예외 되던지기를 해서 호출한 메서드로 예외를 전달하는 방법을 쓰면  return 문이 없어도 된다. 그래서 검증에서도  assert 문 대신  AssertError 를 생성해서 던진다. 

 

 

 

 

연결된 예외(Chained Exception)

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

 

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

예를 들어 이런 코드가 있다고 해보자. 위의 코드는  InstallException 을 생성한 수에  initCause( )  SpaceException 을  InstallException 의 원인 예외로 등록한다. 그리고  throw 로 이 예외를 던진다. 발생한 예외를 그냥 처리하지 않고 원인 예외로 등록해서 다시 예외를 발생시키는 이유는 여러 가지 예외를 하나의 큰 분류인 예외로 묶어서 다루기 위해서이다. 

 

 

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

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

 

 

try{
    startInstall();	//SpaceException 발생
    copyfiles();
}catch(InstallException e){	//InstallException은
    e.printStackTrace();	//SpaceException과 MemoryException의 부모로 함
}

그렇다고 코드를 위와 같이  InstallException  SpaceException  MemoryException 의 부모로 지정하는  catch 블럭을 작성하면, 실제로 발생한 예외가 어떤 예외인지 알 수 없다는 문제가 생긴다. 그리고  SpaceException  MemoryException 의 상속관계를 변경하기도 어렵다. 그래서 나온 것이 예외가 원인 예외를 포함시킬 수 있도록 하는 방법이다. 이렇게 하면 두 예외는 상속관계가 아니어도 상관없다. 

 

 

RuntimeException(Throwable cause) : 원인예외를 등록하는 생성자

또 다른 이유는 'checked예외'를 'unchecked예외'로 바꿀 수 있도록 하기 위해서이다. checked예외가 발생해도 예외를 처리할 수 없는 상황이 생기면 의미 없는  try - catch 문을 추가하는 것뿐이었는데 checked예외를 unchecked예외로 바꾸면 예외처리가 선택적이 되므로 억지로 예외처리를 하지 않아도 된다. 예를 들어  MemoryException  Exception 의 자식이므로 반드시 예외처리를 해야 하는데, 이 예외를  RuntimeException( ) 으로 감싸버리면 unchecked예외가 되어 더 이상 예외처리를 하지 않아도 된다.

 

 

 

+ Recent posts