예외 처리

LeeKyoungChang·2022년 2월 20일
0
post-thumbnail

Java의 정석 의 책을 읽고 정리한 내용입니다.

 

📚 1. 예외처리(exception handling)

📖 A. 프로그램 오류

  • 프로그램이 실행 중 어떤 원인에 의해서 오작동을 하거나 비정상적으로 종료되는 경우가 있다.
  • 이러한 결과를 초래하는 원인을 프로그램 에러 또는 오류라고 한다.
컴파일 에러 : 컴파일 시에 발생하는 에러
런타임 에러 : 실행 시에 발생하는 에러
논리적 에러 : 실행은 되지만, 의도와 다르게 동작하는 것
에러 (error) : 프로그램 코드에 의해서 수습될 수 없는 심각한 오류  
예외 (exception) : 프로그램 코드에 의해서 수습될 수 있는 다소 미약한 오류

 

📖 B. 예외 클래스의 계층구조

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

스크린샷 2022-02-20 오후 3 17 11
(1) Exception 클래스와 그 자손들 (그림 윗부분, RuntimeException과 자손들 제외)
(2) RuntimeException 클래스와 그 자손들 (그림 아랫 부분)
  • RuntimeException 클래스와 그 자손 클래스들 : RuntimeException 클래스들
  • RuntimeException 클래스들을 제외한 나머지 클래스 : Exception 클래스들

 

Exception 클래스들 : 사용장 실수와 같은 외적인 요인에 의해 발생하는 예외
RuntimeException 클래스들 : 프로그래머의 실수로 발생하는 예외

 

📖 C. 예외처리하기 - try - catch 문

🔔 예외 처리(exception handling)란?
프로그램 실행 시 발생할 수 있는 예기치 못한 예외의 발생에 대비한 코드를 작성하는 것이며, 예외처리의 목적은 예외의 발생으로 인한 실행 중인 프로그램의 갑작스런 비정상 종료를 막고, 정상적인 실행 상태를 유지할 수 있도록 하는 것이다.

예외처리 (exception handling) 의
      정의 : 프로그램 실행시 발생할 수 있는 예외의 발생에 대비한 코드를 작성하는 것
      목적 : 프로그램의 비정상 종료를 막고, 정상적인 실행 상태를 유지하는 것
  • 발생한 예외를 처리하지 못하면, 프로그램은 비정상적으로 종료되며, 처리되지 못한 예외 (uncaught exception)는 JVM의 예외처리기 (UncaughtExceptionHandler) 가 받아서 예외의 원인을 화면에 출력한다.

 

📖 D. try - catch 문에서의 흐름

🔔 두가지 경우에 따른 문장 실행순서

▶ try 불럭 내에서 예외가 발생한 경우,
(1) 발생한 예외와 일치하는 catch 블럭이 있는지 확인한다.
(2) 일치하는 catch 블럭을 찾게 되면, 그 catch 블럭 내의 문장들을 수행하고 전체 try-catch 문을 빠져나가서 그 다음 문장을 계속해서 수행한다. 만일 일치하는 catch 블럭을 찾지 못하면, 예외는 처리되지 못한다.

▶ try 블럭 내에서 예외가 발생하지 않은 경우,
(1) catch 블럭을 거치지 않고 전체 try-catch 문을 빠져나가서 수행을 계속한다.

 

📖 E. 예외의 발생과 catch블럭

첫 번째 catch 블럭부터 차례로 내려가면서 catch 블럭의 괄호 () 내에 선언된 참조 변수의 종류와 생성된 예외 클래스의 인스턴스에 instanceof 연산자를 이용해서 검사하게 되는데, 검사결과가 truecatch 블럭을 만날 때까지 검사는 계속된다.

 

✔️ printStackTrace()와 getMessage()

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

 

✔️ 멀티 catch 블럭

  • JDK 1.7 부터 여러 catch 블럭을 | 기호를 이용해서, 하나의 catch 블럭으로 합칠 수 있게 되었으며, 이를 멀티 catch 블럭 이라 한다.
  • |기호로 연결할 수 있는 예외 클래스의 개수에는 제한이 없다.
  • 멀티 catch 블럭에 사용되는 |는 논리 연산자가 아니라 기호이다.
try {
	
   ...
	   
} catch (ExceptionA e) {
   e.printStackTrace();
} catch (ExceptionB e2) {
   e2.printStackTrace();
} 

➡️

try {
	
   ...
	   
} catch (ExceptionA | ExceptionB e) {
   e.printStackTrace();
} 
  • 만일 멀티 catch 블럭| 기호로 연결된 예외 클래스가 조상과 자손의 관계에 있다면 컴파일 에러가 발생한다.
try {

     ...

} catch (ParentException | ChildException e) {  // 에러!
     e.printStackTrace();
}
  • 왜냐하면, 두 예외 클래스가 조상과 자손의 관계에 있다면, 그냥 다음과 같이 조상 클래스만 써주는 것과 똑같기 때문이다.
  • 불필요한 코드는 제거하라는 의미에서 에러가 발생하는 것이다.
try {

     ...

} catch (ParentException e) { 
     e.printStackTrace();
}
  • 참조 변수 e로 멀티 catch 블럭| 기호로 연결된 예외 클래스들의 공통 분모인 조상 예외 클래스에 선언된 멤버만 사용할 수 있다.

 

try {
     ...
} catch (ExceptionA | ExceptionB e) {
     e.methodA();  // 에러. ExceptionA에 선언된 methodA()는 호출불가
	
     if (e instanceof ExceptionA) {
           ExceptionA e1 = (ExceptionA) e;
           e1.methodA();  // OK. ExceptionA에 선언된 메서드 호출가능
     } else {   // if ( e instanceof ExceptionB)
          ...
     }
     e.printStackTrace();
}
  • 필요하다면, 위와 같이 instanceof로 어떤 예외가 발생한 것인지 확인하고 개별적으로 처리할 수는 있다.
  • 그러나 이렇게까지 해가면서 catch 블럭을 합칠 일은 거의 없을 것이다.
  • 여러 catch 블럭을 멀티 catch 블럭으로 합치는 경우는 대부분 코드를 간단히 하는 정도의 수준일 것이므로 이러한 제약에 대해 너무 고민하지 않아도 된다.

 

📖 F. 예외 발생시키기

  • 키워드는 throw를 사용해서 프로그래머가 고의로 예외를 발생시킬 수 있다.
순서
(1) 먼저, 연산자 new를 이용해서 발생시키려는 예외 클래스의 객체를 만든 다음
- Exception e = new Exception("고의로 발생시켰음");

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

 

class ExceptionEx9 {

   public static void main(String[] args) {
       try {
           Exception e = new Exception("고의로 발생시켰음.");
           throw e;     // 예외를 발생시킴
       //  throw new Exception("고의로 발생시켰음.");  // 위의 두 줄을 한줄로 줄여 쓸 수 있다.
       } catch (Exception e)        {
           System.out.println("에러 메시지 : " + e.getMessage());
            e.prinStackTrace();
       }
       System.out.println("프로그램이 정상 종료되었음.");
   }
}
에러 메시지 : 고의로 발생시켰음.
java.lang.Exception : 고의로 발생시켰음.
      at ExceptionEx9.main (ExceptionEx9.java : 4)
프로그램이 정상 종료되었음.
  • Exception 인스턴스를 생성할 때, 생성자에 String을 넣어 주면, 이 StringException 인스턴스에 메시지로 저장된다.
  • 이 메시지는 getMessage()를 이용해서 얻을 수 있다.

 

💡 참고

  • 컴파일러가 예외처리를 확인하지 않는 RuntimeException 클래스들 : unchecked 예외
  • 예외처리를 확인하는 Exception 클래스 : checked 예외

 

📖 G. 메서드에 예외 선언하기

  • 메서드에 예외를 선언하려면, 메서드의 선언부에 키워드 throw를 사용해서 메서드 내에서 발생할 수 있는 예외를 적어주기만 하면 된다.
  • 예외가 여러 개일 경우에는 쉼표 , 로 구분한다.
void method() throws Exception1, Exception2, ... ExceptionN {
     // 메서드의 내용 
}

 

💡 참고
예외를 발생시키는 키워드 throw와 예외를 메서드에 선언할 때 쓰이는 throws를 잘 구별하자.

 

  • 메서드의 선언부에 예외를 선언함으로써 메서드를 사용하려는 사람이 메서드의 선언부를 보았을 때, 이 메서드를 사용하기 위해서는 어떠한 예외들이 처리되어져야 하는지 쉽게 알 수 있다.
  • 메서드에 예외를 선언할 때 일반적으로 RuntimeException 클래스들은 적지 않는다.
  • 이 들을 메서드 선언부의 throws 에 선언한다고 해서 문제가 되지는 않지만, 보통 반드시 처리해주어야 하는 예외들만 선언한다.
  • 이처럼 Java API 문서를 통해 사용하고자 하는 메서드의 선언부와 Throws 를 보고, 이 메서드에서는 어떤 예외가 발생할 수 있으며 반드시 처리해주어야 하는 예외는 어떤 것들이 있는지 확인하는 것이 좋다.

 

class ExceptionEx12 {
   public static void main(String[] args) throws Exception {
       method1();   // 같은 클래스내의 static 멤버이므로 객체생성없이 직접 호출 가능.
   }   // main 메서드의 끝
   static void method1() throws Exception {
       method2();
   }   // method1의 끝
   static void method2() throws Exception {
       throw new Exception();
   }   // method2의 끝
}
java.lang.Exception
      at  ExceptionEx12.method2 (ExceptionEx12.java : 11)
      at  ExceptionEx12.method1 (ExceptionEx12.java : 7)
      at  ExceptionEx12.main(ExceptionEx12.java : 3)
(1) 예외가 발생했을 때, 모두 3개의 메서드 (main, method1, method2)가 호출 스택에 있었으며, 
(2) 예외가 발생한 곳은 제일 윗줄에 있는 method2() 라는 것과 
(3) main메서드가 method1()을, 그리고 method1()은 method2()를 호출했다는 것을 알 수 있다.           

 

class ExceptionEx14 {

   public static void main(String[] args) {
      try {
              method1();
      } catch (Exception e)         {
              System.out.println("main 메서드에서 예외가 처리되었습니다.");
              e.printStackTrace();
      }
   }   // main 메서드의 끝
   static void method1() throws Exception {
       throw new Exception();
   }   // method1 ()의 끝
}  // class의 끝
main 메서드에서 예외가 처리되었습니다.
java.lang.Exception
      at  ExceptionEx14.method1(ExceptionEx14.java : 12)
      at  ExceptionEx14.main(ExceptionEx14.java : 4)
  • ExceptionEx14클래스처럼 예외가 발생한 메서드에서 예외를 처리하지 않고 호출한 메서드로 넘겨주면, 호출한 메서드에서는 method1() 을 호출한 라인에서 예외가 발생한 것으로 간주되어 이에 대한 처리를 하게 된다.
  • 이처럼 예외가 발생한 메서드 method1() 에서 예외를 처리할 수도 있고, 예외가 발생한 메서드를 호출한 메서드 main메서드 에서 처리할 수도 있다.

 

import java.io.*;

class ExceptionEx15 {

   public static void main(String[] args) {
       // command line에서 입력받은 값을 이름으로 갖는 파일을 생성한다.
       File f = createFile(args[0]);
       System.out.println( f.getName() + " 파일이 성공적으로 생성되었습니다.");
   }  // main 메서드의 끝

   static File createFile(String fileName) {
       try {
           if (fileName == null || fileName.equals(""));
               throw new Exception("파일이름이 유효하지 않습니다.");
       } catch (Exception e) {
         // fileName이 부적절한 경우, 파일 이름을 '제목없음.txt'로 한다.
         fileName = "제목없음.txt";
       } finally {
            File f = new File(fileName);  // File 클래스의 객체를 만든다.
            createNewFile(f);             // 생성된 객체를 이요해서 파일을 생성한다.
            return f;
       }
   }   // createFile 메서드의 끝

   static void createNewFile(File f) {
       try {
           f.createNewFile();         // 파일을 생성한다.
       } catch(Exception e) { }
       // createNewFile 메서드의 끝
   }
}
  • 이 예제는 예외가 발생한 메서드에서 직접 예외를 처리하도록 되어 있다.

 

import java.io.*;

class ExceptionEx16 {
   public static void main(String[] args) {
       try {
           File f = createFile(args[0]);
           System.out.println( f.getName() + " 파일이 성공적으로 생성되었습니다.");
       } catch (Exception e) {
           System.out.println(e.getMessage() + " 다시 입력해 주시기 바랍니다.");
       }
   }   // main 메서드의 끝

   static File createFile(String fileName) throws Exception {
       if (fileName == null || fileName.equals(""));
           throw new Exception("파일이름이 유효하지 않습니다.");
       File f = new File(fileName);  // File 클래스의 객체를 만든다.
       // File 객체의 createNewFile 메서드를 이용해서 실제 파일을 생성한다.
       f.createNewFile();
       return f;        // 생성된 객체의 참조를 반환한다. 
       }
   }   // createFile 메서드의 끝
}  // 클래스의 끝

ExceptionEx15ExceptionEx16의 차이점은 예외의 처리방법

  • ExceptionEx15는 예외가 발생한 createFile 메서드 자체 내에서 처리를 하며, ExceptionEx16createFile 메서드를 호출한 메서드 main메서드 에서 처리한다.
  • 이처럼 예외가 발생한 메서드 내에서 자체적으로 처리해도 되는 것은 메서드 내에서 try-catch 문을 사용해서 처리하고, 두 번째 예제처럼 메서드에 호출 시 넘겨받아야 할 값 fileName을 다시 받아야 하는 경우 (메서드에서 자체적으로 해결이 안 되는 경우) 에는 예외를 메서드에 선언해서, 호출한 메서드에서 처리해야 한다.

 

📖 H. finally 블럭

  • finally 블럭은 try-catch 문과 함께, 예외의 발생여부에 상관없이 실행되어야할 코드를 포함시킬 목적으로 사용된다.
  • try-catch 문의 끝에 선택적으로 덧붙여 사용할 수 있으며, try-catch-finally 의 순서로 구성된다.
try {
    // 예외가 발생할 가능성이 있는 문장들을 넣는다.
} catch (Exception1 el) {
     // 예외처리를 위한 문장을 적는다.
} finally {
     // 예외의 발생여부에 관계없이 항상 수행되어야하는 문장들을 넣는다.
     // finally 블럭은 try-catch 문의 맨 마지막에 위치해야한다.
}
  • 예외가 발생한 경우에는 try → catch → finally 의 순으로 실행되고, 예외가 발생하지 않은 경우에는 try → finally 의 순으로 실행된다.

 

class FinallyTest3 {
   public static void main(String[] args) {
       // method1()은 static 메서드 이므로 인스턴스 생성없이 직접 호출이 가능하다.
       FinallyTest3.method1();
       System.out.println("method1()의 수행을 마치고 main메서드로 돌아왔습니다.");
   }   // main 메서드의 끝

   static void method1() 
       try {
           System.out.println("method1() 이 호출되었습니다.");

           return;   // 현재 실행 중인 메서드를 종료한다.
       } catch (Exception e)            {
           e.printStackTrace();
       } finally {
           System.out.println("method1 ()의 finally블럭이 실행되었습니다.");
       }
   }   // method1 메서드의 끝
}
method1 () 이 호출되었습니다.
method1 () 의 finally블럭이 실행되었습니다.
method1 () 의 수행을 마치고 main메서드로 돌아왔습니다.
  • try 블럭에서 return문이 실행되는 경우에도 finally 블럭의 문장들이 먼저 실행된 후에, 현재 실행 중인 메서드를 종료한다.
  • 이와 마찬가지로 catch 블럭의 문장 수행중에 return문을 만나도 finally 블럭의 문장들은 수행된다.

 

📖 I. 자동 자원 반환 - try - with - resources문

  • JDK 1.7 부터 try-with-resources 문이라는 try-catch 문의 변형이 새로 추가되었다.
  • 15장 입출력 (I/0)과 관련된 클래스를 사용할 때 유용하다.
  • 주로 입출력에 사용되는 클래스 중에서는 사용한 후에 꼭 닫아 줘야 하는 것들이 있다.
  • 그래야 사용했던 자원 (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 블럭 내에서만 사용할 수 있다.

 

class TryWithResourceEx {
   
   public static void main(String[] args) {

       try (CloseableResource cr = new CloseableResource()) {
           cr.exceptionWork(false); // 예외가 발생하지 않는다.
       } 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();
       }
   }     // main의 끝

}

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);  }
}
exceptionWork(false) 가 호출됨
close() 가 호출됨
CloseException : CloseException 발생 !!!
        at CloseableResource.close(TryWithResourceEx.java)
        at TryWithResourceEx.main(TryWithResourceEx.java)

exceptionWork(false) 가 호출됨
close() 가 호출됨
WorkException : WorkException 발생 !!!
        at CloseableResource.exceptionWork(TryWithResourceEx.java)
        at TryWithResourceEx.main(TryWithResourceEx.java)
        Suppressed : CloseException : CloseException 발생 !!!
                at CloseableResource.close(TryWithResourceEx.java)
                at TryWithResourceEx.main(TryWithResourceExjava)
  • main메서드에 두 개의 try-catch 문이 있는데, 첫 번째 것은 close() 에서만 예외를 발생시키고, 두 번째 것은 exceptionWork()close() 에서 모두 예외를 발생시킨다.
  • 두 예외가 동시에 발생할 수는 없기 때문에, 실제 발생한 예외를 WorkException으로하고, CloseException 은 억제된 예외로 다룬다.
  • 억제된 예외에 대한 정보는 실제로 발생한 예외인 WorkException 에 저장된다.

 

Throwable 에는 억제된 예외와 관련된 메서드가 정의되어 있다.

void addSuppressed(Throwable exception) : 억제된 예외를 추가
Throwable[] getSuppressed() : 억제된 예외 (배열) 를 반환
  • 만일 기존의 try-catch 문을 사용했다면, 먼저 발생한 WorkException 은 무시되고, 마지막으로 발생한 CloseException 에 대한 내용만 출력되었을 것이다.

 

✔️ 사용자정의 예외 만들기

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

 

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

  • 한 메서드에서 발생할 수 있는 예외가 여럿인 경우, 몇 개는 try-catch 문을 통해서 메서드 내에서 자체적으로 처리하고, 그 나머지는 선언부에 지정하여 호출한 메서드에서 처리하도록 함으로써, 양쪽에서 나눠서 처리되도록 할 수 있다.
  • 그리고 심지어는 단 하나의 예외에 대해서도 예외가 발생한 메서드와 호출한 메서드, 양쪽에서 처리하도록 할 수 있다.
  • 이것은 예외를 처리한 후에 인위적으로 다시 발생시키는 방법을 통해서 가능한데, 이것을 예외 되던지기 (Exception Re-throwing) 라고 한다.
  • 이 방법은 하나의 예외에 대해서 예외가 발생한 메서드와 이를 호출한 메서드 양쪽 모두에서 처리해줘야 할 작업이 있을 때 사용된다.
  • 이 때 주의해야할 점은 예외가 발생할 메서드에서는 try-catch 문을 사용해서 예외처리를 해줌과 동시에 메서드의 선언부에 발생할 예외를 throws 에 지정해줘야 한다는 것이다.
class ExceptionEx17 {

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

   static void method1() throws Exception {
       try {
           throw new Exception();
       } catch (Exception e) {
           System.out.println("method1 메서드에서 예외가 처리되었습니다.");
           throw e;                    // 다시 예외를 발생시킨다.
       }
   }   // method1 메서드의 끝
}
method1 메서드에서 예외가 처리되었습니다.
main 메서드에서 예외가 처리되었습니다.
  • 결과에서 알 수 있듯이 method1()main메서드 양쪽의 catch 블럭이 모두 수행되었음을 알 수 있다.

 

static int method1() {
     try {
           System.out.println("method1() 이 호출되었습니다.");
           return 0;            // 현재 실행 중인 메서드를 종료한다.
     } catch (Exception e) {
           e.printStackTrace();
           return 1;            // catch 블럭 내에도 return 문이 필요하다.
     } finally {
           System.out.println("method1()의 finally 블럭이 실행되었습니다.");
     }
}    // method1 메서드의 끝
  • 반환값이 있는 return 문의 경우, catch 블럭에도 return 문이 있어야 한다.
  • 예외가 발생했을 경우에도 값을 반환해야하기 때문이다.

 

static int method1() throws Exception {   // 예외를 선언해야 함
     try {
           System.out.println("method1() 이 호출되었습니다.");
           return 0;              // 현재 실행 중인 메서드를 종료한다.
     } catch (Exception e) {
           e.printStackTrace();
   //      return 1;              // catch 블럭 내에도 return 문이 필요하다.
           throw new Exception(); // return 문 대신 예외를 호출한 메서드로 전달.
     } finally {
           System.out.println("method1()의 finally 블럭이 실행되었습니다.");
     }
}    // method1 메서드의 끝
  • 또는 catch 블럭에서 예외 던지기를 해서 호출한 메서드로 예외를 전달하면, return 문이 없어도 된다.

 

💡 참고

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

 

📖 K. 연결된 예외 (chained exception)

  • 한 예외가 다른 예외를 발생시킬 수도 있다.
  • 예를 들어 예외 A가 예외 B를 발생시켰다면, A를 B의 원인 예외 (cause exception) 라고 한다.
Throwable initCause (Throwable cause) : 지정한 예외를 원인 예외로 등록
Throwable getCause() : 원인 예외를 반환
  • initCause()Exception 클래스의 조상인 Throwable 클래스에 정의되어 있기 때문에 모든 예외에서 사용가능하다.
  • 발생한 예외를 그냥 처리하면 될 텐데, 원인 예외로 등록해서 다시 예외를 발생시키는지 궁금할 것이다.
    ➡️ 그 이유는 여러가지 예외를 하나의 큰 분류의 예외로 묶어서 다루기 위해서이다.

 

try {
     startInstall();	// SpaceException 발생
     copyFiles();
} catch (InstallException e)   {	// InstallException 은
     e.printStackTrace();	// SpaceException 과 MemoryException의 조상
}
  • 그렇다고 이와 같이 InstallExceptionSpaceExceptionMemoryException 의 조상으로 해서 catch 블럭을 작성하면, 실제로 발생한 예외가 어떤 것인지 알 수 없다는 문제가 생긴다.
  • 그리고 SpaceExceptionMemoryException 의 상속 관계를 변경해야 한다는 것도 부담이다.

 

public class Throwable implements Serializable {
        ...
     private Throwable cause = this; // 객체 자신 (this) 을 원인 예외로 등록
         ...
}
  • 그래서 생각한 것이 예외가 원인 예외를 포함시킬 수 있게 한 것이다.
  • 이렇게 하면, 두 예외는 상속관계가 아니어도 상관없다.

 

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

  • checked 예외가 발생해도 예외를 처리할 수 없는 상황이 하나둘 발생하기 시작했다.
  • checked 예외unchecked 예외로 바꾸면 예외 처리가 선택적이 되므로 억지로 예외 처리를 하지 않아도 된다.
static void startInstall() throws SpaceException, MemoryException {
  if (!enoughSpace())             // 충분한 설치 공간이 없으면 ...
     throw new SpaceException("설치할 공간이 부족합니다.");

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

➡️

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

  if (!enoughMemory())            // 충분한 메모리가 없으면 ...
    throw new RuntimeException(new MemoryException("메모리가 부족합니다."));
}
  • MemoryExceptionException의 자손이므로 반드시 예외를 처리해야하는데, 이 예외를 RuntimeException 으로 감싸버렸기 때문에 unchecked 예외가 되었다.
  • 그래서 더 이상 startInstall() 의 선언부에 MemoryException 을 선언하지 않아도 된다.
  • 참고로 위의 코드에서는 initCause() 대신 RuntimeException 의 생성자를 사용했다.

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

 

✔️ 지금까지 배운 내용 관련 소스

class ChainedExceptionEx {
    public static void main(String args[]) {
        try {
            install();
        } catch (InstallException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    static void install() throws InstallException {
        try {
            startInstall();        // 프로그램 설치에 필요한 준비를 한다.
            copyFiles();               // 파일들을 복사한다.
        } catch (SpaceException e) {
            InstallException ie = new InstallException("설치중 예외발생");
            ie.initCause(e);
            throw ie;
        } catch (MemoryException me) {
            InstallException ie = new
                    InstallException("설치중 예외발생");
            ie.initCause(me);
            throw ie;
        } finally {
            deleteTempFiles();           // 프로그램 설치에 사용된 임시파일들을 삭제한다.
        } // try의 끝
    }

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

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

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

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

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

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

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

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

class MemoryException extends Exception {
    MemoryException(String msg) {
        super(msg);
    }
}          
InstallException : 설치 중 예외발생
        at ChainedExceptionEx.install(ChainedExceptionEx.java)
        at ChainedExceptionEx.main(ChainedExceptionEx.java);
Caused by : SpaceException : 설치할 공간이 부족합니다.
        at ChainedExceptionEx.startInstall(ChainedException.java)
        at ChainedExceptionEx.install(ChainedExceptionEx.java)
       ... 1 more
profile
"야, (오류 만났어?) 너두 (해결) 할 수 있어"

0개의 댓글

관련 채용 정보