IT_Programming/Java

예외처리

JJun ™ 2006. 2. 27. 16:04
가. 예외와 예외처리 방법

도스에서부터 프로그램을 작성해보신 분들이라면 아시겠지만, 프로그램이 실행되는 동안 예기치 못한 여러 가지 에러들이 발생하게 됩니다. 파일을 열려고 하는데 파일이 없다거나, A드라이브에 있는 파일들의 리스트를 보려고 하는데 A 드라이브에 디스켓이 없다거나, 또는 프로그램을 잘못 작성하여 0으로 나누게 되어 ‘Divide by 0’라는 에러가 발생하면서 프로그램이 정지된다거나 하는 등의 프로그램을 정지시키거나 시스템을 정지시킬 수 있는 에러들이 흔히 발생하게 됩니다. 이러한 에러들에 대해서 자바에서는 예외라고 합니다. 그리고, 이러한 예외가 발생했을 경우 프로그램 개발자가 처리할 수 있도록 하기 위해 예외의 종류를 정의하고, 언어 수준에서 예외를 처리할 수 있는 구문들을 제공해 주고 있습니다. 이렇게 예외가 발생했더라도 프로그램이 비정상적으로 종료되지 않고, 예외를 처리하게 함으로서 좀 더 안정적이고 신뢰성 있는 프로그램을 작성할 수 있도록 해 주는 것입니다.

자바에서 제공되는 예외처리에는 두 가지 방법이 있습니다. 하나는 try/catch/finally 구문을 사용하여 예외가 발생한 곳에서 직접 처리하는 것이고, 다른 방법은 발생한 예외를 자신을 호출한 곳으로 throws 구문을 이용하여 던지는 것입니다. 이 때, 이 예외를 받는 곳은 이 예외가 발생한 곳이 main 메소드가 아닌 메소드일 경우에는 main 메소드 또는 다른 메소드가 될 것이고, 예외가 발생한 곳이 main 메소드이면 자바 가상머신이 이 예외를 받아 처리를 하게 되겠지요. 왜냐하면, main 메소드는 자바 가상머신이 호출하기 때문입니다.

나. 예외처리 기본 구문 - try/catch 문

예외처리를 위한 기본적인 구문인 try/catch 구문이 있습니다. try/catch 구문의 예외처리 방법은 간단합니다. 먼저, 예외가 발생할 여지가 있는 문장들을 try 블록 내에 위치시킵니다. 그리고, 예외가 발생했을 경우 이를 처리하기 위한 문장들을 catch 블록 내에 위치시킵니다. try 블록과 catch 블록의 기본 구조는 다음과 같습니다.

 try {

// 예외를 발생시키는 문장

}
catch(ExceptionType-1  exceptionName-n) {

// 예외를 처리하는 문장

}
…  // 다른 catch 문들
catch(ExceptionType-n  exceptionName-n) {

// 예외를 처리하는 문장

}

<그림 1.try/catch 구문의 기본 구조>

 

  • try 블록: try 블록 내에는 예외를 발생시킬 수 있는 문장들을 넣게 됩니다. 이러한 try 블록 내의 문장을 수행하다가 예외가 발생하면, catch 블록이나 바로 다음에서 소개할 finally 블록으로 그 수행을 넘겨 예외를 처리할 수 있도록 합니다. 그러나, try 블록 내의 문장을 수행할 때 아무런 예외도 발생하지 않는다면, 이 try 블록과 연결된 catch 블록이나 finally 블록을 건너띄게 됩니다. 왜냐하면, catch 블록이나 finally 블록의 목적은 예외가 발생했을 경우, 이 예외를 처리하기 위한 것이기 때문입니다. 이 때, try 블록 내에 위치한 문장들은 하나의 예외를 발생시킬 수도 있고, 아니면 여러 개의 예외를 발생시킬 수도 있습니다. 따라서, catch 블록은 이러한 모든 예외를 처리할 수 있도록 해 주어야 합니다. 마지막으로, 중첩된 try 블록 구조를 이용하여 예외를 처리할 수도 있습니다.
  • catch 블록: try 블록은 항상 catch 블록이나 finally 블록과 함께 사용되어야 합니다. catch 블록은 try 블록 내에서 발생된 예외를 받아 처리하는 부분입니다. catch 블록은 try 블록 다음에 위치해야 하며, 그 사이에는 다른 어떠한 문장도 쓸 수 없습니다. 하나의 try 블록은 여러 개의 catch 블록과 같이 사용될 수 있고, 이렇게 함으로써 try 블록 내에서 발생할 수 있는 모든 예외를 처리할 수 있도록 하고 있습니다. 이 때, 각 catch 블록은 매개변수를 통해서 처리할 예외의 형식을 지정하게 됩니다. 다시 말해서, catch 블록은 매개변수를 통해서 처리할 예외를 넘겨받게 됩니다.
  • 이 때, catch 블록을 사용할 때 주의해야 할 사항이 있는데, 예외처리 블록인 catch 블록을 위치시킬 때 상위자료형과 하위자료형의 관계를 따져, 하위자료형인 예외를 처리하는 catch 블록을 앞에 위치시켜야 합니다. 다음의 예제를 먼저 살펴보도록 하겠습니다.

     class ExceptionTest {
       public static void main(String args[]) {
          int x=10, y=0, z;
          try {
             System.out.print("Try ");
             z = x / y;
          }
          catch(Exception e) {
             System.out.println("Exception: " + e);
          }
          catch(ArithmeticException e) {
             System.out.println("ArithmeticException: " + e);
          }
       }
    }
     
    /*
     * Results: 
     D:\AIIT\JAVA\Working\Exam\04>javac ExceptionTest.java
     ExceptionTest.java:11: catch not reached.
           catch(ArithmeticException e) {
           ^
     1 error
     
     D:\AIIT\JAVA\Working\Exam\04> 
     */

    <프로그램 1.ExceptionTest.java>

    예외의 상속 관계에 대해서 뒤에서 설명하겠지만, 먼저 간단하게 소개하면 모든 예외의 최상위 클래스는 Exception 클래스입니다. 따라서, 위의 자바 예제 프로그램에서는 Exception 클래스가 ArithmeticException 클래스보다 더 상위클래스이므로 상위자료형이 됩니다. 앞에서 살펴보았듯이, 상위자료형의 객체는 하위자료형의 객체를 가리킬 수 있도록 되어 있습니다. 따라서, 위와 같은 컴파일 에러가 발생하게 됩니다. 어떤 예외가 발생하더라도 첫 번째 catch 블록에서 예외를 처리하기 때문에, 만약 다른 catch 블록이 있더라도, 첫 번째 이후의 다른 catch 블록에는 그 기회가 주어지지 않게 됩니다. 그러므로, 위의 예제에서는 하위자료형인 ArithmeticException 예외를 처리하기 위한 catch 블록을 상위자료형인 Exception 예외를 처리하기 위한 catch 블록 앞에 두어야 합니다.

    다음에 나오는 예제는 위의 자바 프로그램을 수정하여 다시 작성한 프로그램입니다. 다음의 실행 결과를 확인하여 보십시요.

     class ExceptionTest2 {
       public static void main(String args[]) {
          int x=10, y0=0, y1=2, z=10;
           System.out.println("main: z="+z);
          try {
             System.out.println("try: before 'divide by 0'");
    //         z = x / y0;   // (1)
             z = x / y1;   // (2)
             System.out.println("try: after 'divide by 0'");
          }
          catch(ArithmeticException e) {
             System.out.println("catch(ArithmeticException): " + e);
          }
          catch(Exception e) {
             System.out.println("catch(Exception): " + e);
          }
          System.out.println("main: z="+z);
       }
    }
     
    /*
     * Results: 
     (1) 부분을 수행한 경우, (2) 부분을 주석처리
     D:\AIIT\JAVA\Working\Exam\04>java ExceptionTest2
     main: z=10
     try: before 'divide by 0'
     catch(ArithmeticException): java.lang.ArithmeticException: / by zero
     main: z=10
     
     D:\AIIT\JAVA\Working\Exam\04>
     
     (2) 부분을 수행한 경우, (1) 부분을 주석처리
     D:\AIIT\JAVA\Working\Exam\04>java ExceptionTest2
     main: z=10
     try: before 'divide by 0'
     try: after 'divide by 0'
     main: z=5
      D:\AIIT\JAVA\Working\Exam\04> 
     */

    <프로그램 2.ExceptionTest2.java>

    위의 자바 프로그램은 다음과 같이 두 가지 방법으로 실행해 보았고, 실행 순서를 살펴보면 다음과 같습니다.

    • try 블록 내에서 (1) 부분을 실행한 경우:
      먼저, 정상적으로 try 블록 이전까지의 문장들을 실행하고, ‘/ 연산에서는 0으로 나눌 경우 ArithmeticException 예외가 발생할 수 있으므로 이 문장을 try 블록 내에 위치시켜 실행 시켰는데, y0(=0)로 나눔으로써 ArithmeticException 예외가 발생하여 그 다음 문장을 실행시키지 않고, try 블록과 연결된 catch 블록 중 ArithmeticException 예외를 받아 처리하는 catch 블록이 있으므로 수행은 그 블록으로 옮겨지게 되어 블록 내에서  예외를 처리하고,예외가 처리되었으므로 try/catch 구문 다음으로 수행을 옮겨 나머지 문장들을 실행하게 됩니다.
    • try 블록 내에서 (2) 부분을 실행 한 경우:
      먼저, 정상적으로 try 블록 이전까지의 문장들을 실행하고, ‘/ 연산에서는 0으로 나눌 경우 ArithmeticException 예외가 발생할 수 있으므로 이 문장을 try 블록 내에 위치시켜 실행 시켰는데, y1(=2)으로 나눔으로써 아무런 예외도 발생하지 않았으므로, 그 다음 출력하기 위한 문장을 실행시키고, try 블록과 연결된 catch 블록 중 어느 블록도 실행하지 않고, try/catch 구문 다음으로 수행을 옮겨 나머지 문장들을 실행하게 됩니다.
  • 다. 예외처리 추가 구문 - finally 문

    try/catch 구문과 함께 선택적으로 사용될 수 있는 구문으로 finally 블록이 있습니다. try/catch 구문에서 예외가 발생하거나 발생하지 않더라도 반드시 실행해야 하는 문장들이 있을 때, 바로 finally 블록 내에 위치시키게 됩니다. finally 블록은 try/catch 구문의 제어가 끝난 후에 반드시 처리해야 할 마무리 작업들을 처리하는 구문이므로, try 블록이나 catch 블록 바로 뒤에 위치해야 하며 그 사이에는 어떠한 것도 올 수 없습니다. finally 블록은 예외 발생 시 반환되지 않고 종료되어버린 시스템 자원(resources)을 반드시 반환하도록 해야 한다는 필요성 때문에 만들어진 것으로써, 주로 자료구조 및 시스템 자원을 반환하는 코드가 위치하게 됩니다. 이러한, try 블록, catch 블록, 그리고 finally 블록의 기본 구조는 다음과 같습니다.

    try {

    // 예외를 발생시키는 문장

    }
    catch(ExceptionType-1  exceptionName-n) {

    // 예외를 처리하는 문장

    }
    … 
    // 다른 catch 문들
    catch(ExceptionType-n  exceptionName-n) {

    // 예외를 처리하는 문장

    }
    finally {

    // 예외처리 마무리 문장

    }

    <그림 2.try/catch/finally 구문의 기본 구조>

    예외가 발생하던지, 발생하지 않았든 간에 finally블록은 항상 실행된다는 것을 다음의 자바 프로그램을 살펴보면 알 수 있습니다.

    class ExceptionTest3 {
       public static void main(String args[]) {
          int x=10, y0=0, y1=2, z=10;
          System.out.println("main: z="+z);
          try {
             System.out.println("try: before 'divide by 0'");
             z = x / y0;   // (1)
    //         z = x / y1;   // (2)
             System.out.println("try: after 'divide by 0'");
          }
          catch(ArithmeticException e) {
             System.out.println("catch(ArithmeticException): " + e);
          }
          catch(Exception e) {
             System.out.println("catch(Exception): " + e);
          }
          finally {
             System.out.println("finally: z="+z);
          }
          System.out.println("main: z="+z);
       }
    }
    /*
     * Results:
     (1) 부분을 수행한 경우, (2) 부분을 주석처리
     D:\AIIT\JAVA\Working\Exam\04>java ExceptionTest3
     main: z=10
     try: before 'divide by 0'
     catch(ArithmeticException): java.lang.ArithmeticException: / by zero
     finally: z=10
     main: z=10
     D:\AIIT\JAVA\Working\Exam\04>
     (2) 부분을 수행한 경우, (1) 부분을 주석처리
     D:\AIIT\JAVA\Working\Exam\04>java ExceptionTest3
     main: z=10
     try: before 'divide by 0'
     try: after 'divide by 0'
     finally: z=5
     main: z=5
     D:\AIIT\JAVA\Working\Exam\04>
     */
     

    <프로그램 3.ExceptionTest3.java>

    라. 예외의 분류

    자바에서는 try/catch/finally 구문을 자바 언어 수준에서 제공해 줌으로써, 자바 언어 수준에서 예외를 처리할 수 있도록 해 주고 있습니다. 그런데, 이러한 예외에 대해서 자바에서는 미리 정의해 놓고 있습니다. 다음에 나오는 그림은 이러한 자바에서 정의하고 있는 예외 클래스 계층도를 자세히 보여주고 있습니다.

    [그림 3. 자바에서의 예외 클래스 계층도]

     

    자바에서는 기본적으로 프로그램 내에서 try/catch 구문을 이용하여 예외처리를 하지 못하는 Error와 try/catch 구문을 이용하여 예외처리 가능한 Exception 등 두 가지로 나누어 예외를 정의하고 있습니다. 이 때, Exception은 다시 RuntimeException과 그 외의 다른 Exception으로 구분됩니다. 따라서, 자바에서는 Error, Exception을 상속하는 RuntimeException, 그리고 마지막으로 Exception 을 상속하는 RuntimeException 이외의 예외들 등 크게 세가지로 예외를 분류하고 있습니다.

    Error는 AWTError, LinkageError, ThreadDeath, VirtualMachineError 등과 같이 비정상적인 상태를 나타내지만, 자바 프로그램에서는 try/catch 구문을 이용하여 잡지 못하기 때문에 정상적인 상태로 인식하게 됩니다. 다시 말해서, ThreadDeath와 같은 에러가 발생했더라도 자바 프로그램에서는 이 에러를 잡을 방법이 없으므로 정상적인 상태로 인식하게 된다는 것입니다. 그리고, 이러한 에러는 자바 컴파일러를 통해서도 검사하지 못합니다. 그리고, RuntimException은 프로그램 수행 중에 발생하는 에러들에 대해 정의하고 있으므로 물론 컴파일러에서 검사하지 못합니다. 마지막으로, RuntimeException을 제외한 Exception을 상속하는 나머지 예외들은 컴파일러에 의해 검사가능 한 예외입니다. 그러므로, 이러한 예외가 발생가능한 문장들은 반드시 try/catch 구문을 이용하여 예외처리 하는 형식을 취해주어야 하는 것입니다. 이렇게 컴파일러에 의해 검사되지 않는 Error와 RuntimeException 등을 ‘UnChecked Exception’이라 하고, 그렇지 않고 컴파일러에 의해 검사되어지는 예외들을 ‘Checked Exception’라 합니다.

    다음에 나오는 자바 프로그램은‘UnChecked Exception’와 ‘Checked Exception’ 예외에 대한 컴파일러의 반응을 자세히 보여주는 예제입니다.

    class CheckedUncheckedExceptionTest {
       public static void main(String args[]) {
          int a[]=null;
          int i;
          i = a[0];
          System.out.println("i="+i);
          i = System.in.read();  //  (a)
          System.out.println("i="+i);
       }
    }
    /*
     * Results:
     D:\AIIT\JAVA\Working\Exam\04>javac
    CheckedUncheckedExceptionTest.java
     CheckedUncheckedExceptionTest.java:9: Exception java.io.IOException must be
     caught, or it must be declared in the throws clause of this method.
           i = System.in.read();
                             ^
     1 error
     D:\AIIT\JAVA\Working\Exam\04>
     */ 

    <프로그램 4.CheckedUncheckedExceptionTest.java>

    위의 자바 프로그램의 (a) 부분에서 발생시키는 java.io.IOException과 같은 예외의 경우, 자바 컴파일러에 의해 검사가능하고, 이러한 예외는 프로그래머가 처리하는 것이 바람직하기 때문에 자바 컴파일러가 점검하게 됩니다. 이러한 이유로, 이러한 예외를 ‘Checked Exception’이라고 부릅니다.

    다음에 나오는 자바 프로그램은 위의 자바 프로그램 중 컴파일러에 의해 검사된 예외를 try/catch 구문을 이용하여 처리하도록 수정한 프로그램입니다.

    class CheckedUncheckedExceptionTest2 {
       public static void main(String args[]) {
          int a[]=null;
          int i;
          i = a[0];  //  (a)
          System.out.println("i="+i);
          try {
             i = System.in.read();
          }
          catch(java.io.IOException e) {
          }
          System.out.println("i="+i);
       }
    }
    /*
     * Results:
     D:\AIIT\JAVA\Working\Exam\04>java CheckedUncheckedExceptionTest2
     Exception in thread "main" java.lang.NullPointerException:
             at CheckedUncheckedExceptionTest2.main(CheckedUncheckedExceptionTest2.java:6)
     D:\AIIT\JAVA\Working\Exam\04>
     */

    <프로그램 5.CheckedUncheckedExceptionTest.java>

    그런데, 위의 프로그램을 실행하던 중에 (a) 부분에서 null인 배열 객체를 참조한 것을 알게 되었습니다. 이렇게 컴파일 시에는 검사하지 않고 프로그램의 수행 중에 발생하는 예외들은 RuntimeException을 상속하고 있습니다. 위의 자바 프로그램 수행 중 발생한 java.lang.NullPointerException 역시 RuntimeException을 상속하고 있습니다. 마찬가지로, 메모리 부족이나 스레드를 새로 호출하지 못하는 등의 심각한 오류를 나타내는 Error 역시 컴파일 시에는 검사하지 못하기 때문에 ‘UnChecked Exception’으로 분류됩니다.

    RuntimeException은 Exception의 하위클래스(subclass)이므로 예외 처리가  가능하지만, 컴파일러는 이들을 검사하지 않습니다. 컴파일러가 컴파일 시에 이러한 예외를 일일이 검사할 경우, 많은 필요 이상의 손실(overhead)이 발생하기 때문입니다.

    마. 예외 발생시키기 및 처리하기 ? throw, throws

    자바에서는 프로그래머가 원하면 예외를 강제로 발생시킬 수도 있습니다. 자바의 모든 예외는 Throwable 클래스 또는 Throwable 클래스를 상속하는 서브클래스의 인스턴스로 표현됩니다. 또한, 자바에서 제공해 주지 않는 예외에 대해서는 자바 프로그램의 개발자가 새롭게 정의하여 사용할 수 있고, 필요에 따라 이 예외를 발생시킬 수 있습니다.

    자바에서 발생하는 모든 예외(Checked Exception)들에 대해, 발생한 곳에서 직접 처리하거나 처리할 곳을 지정하여 그 예외를 던져주어야 합니다. 다시 말해서, 메소드 내에서 발생한 예외를 해당 메소드가 처리하지 않는다면 예외가 발생되었다는 것을 이 메소드를 호출한 메소드에게 알려서 처리할 수 있도록 해 주어야 하는데, 이러한 과정을 “예외를 던진다”라고 합니다.

    이렇게, 예외가 발생할 가능성이 있는 메소드에서 예외를 직접 처리하지 않고, 이 메소드를 호출한 쪽으로 예외를 던지기 위해서는 메소드의 선언 다음에 throws 문을 사용하여 던지고자 하는 예외들을 나열해 주면 됩니다. 이 때, 한 개 이상의 예외를 던질 수 있고, 던지고자 하는 예외들은 ','를 이용하여 구분해 주면 됩니다.

    메소드선언 throws ExceptionType-1, …, ExceptionType-n {

       …

    }

    사용예)

    public void method() throws IOException {

       …

    }

    메소드에서 자신이 예외를 처리하지 않고 ‘예외를 던진다’고 throws 문을 이용하여 메소드를 선언했을 경우, 실제로 메소드 내에서 던지는 부분이 있어야 합니다. 이를 위해 throw 문을 사용합니다. 그리고, 이렇게 예외를 강제로 발생시키기 위해 자바 프로그램 개발자가 해 주어야 할 일들이 몇 가지가 있습니다.

    • 먼저, throw 문 다음에는 java.lang.Throwable 클래스를 상속 받는 클래스의 인스턴스가 나타나야 합니다.
    • 그리고, throw 문은 try/catch문 안에서 사용되어야 하고,
    • 메소드 선언 시 던져질 예외가 throws 문 다음에 선언되어 있어야 합니다.

     class MyException extends Exception {
       public String toString() {
          return("Divide by zero");
       }
    }
     
    class ThrowExceptionTest {
       public static void main(String args[]) {
          int a = 10, b = 0, c;
     
          try {
             c = myDivide(a, b);
          }
          catch(MyException e) {
             System.out.println(e);
          }
       }
     
       public static int myDivide(int a, int b)
       throws MyException {
          if(b == 0) {
             throw new Exception();            // 에러
          }
     
          return(a/b);
       }
    }
     
    /*
     * Results:
     
     D:\AIIT\JAVA\Working\Exam\04>javac ThrowExceptionTest.java
     ThrowExceptionTest.java:22: Exception java.lang.Exception must be caught,
     or it must be declared in the throws clause of this method.
              throw new Exception();            // 에러
              ^
     1 error
     
     D:\AIIT\JAVA\Working\Exam\04>
      */

    <프로그램 6.ThrowExceptionTest.java>

    위에 나와있는 자바 프로그램을 살펴보면, divide 메소드를 선언할 때 throws 문에 MyExeception 예외만을 던지다고 선언해 놓고, 실제로 ‘// 에러’ 표시된 부분의 throw 문에서는 Exceptioin 객체를 생성하여 던지기 때문에 에러가 발생합니다. Exception이 MyException보다 상위클래스이므로 상위자료형인 Exception 객체는 하위자료형인 MyException 객체가 될 수 없습니다. 따라서, ‘Exception’을 ‘MyException’으로 바꾸어주어야 합니다.

    그리고, 메소드가 throws 문을 가진 메소드를 호출하였다면, 다음 중 하나의 작업을 반드시 해 주어야 하며, 그렇지 않을 경우 컴파일 에러가 발생합니다.

    • try/catch 문을 사용하여 예외를 처리한다.
    • 메소드를 throws로 선언하여 호출한 메소드에게 예외를 던진다.
    • catch 문과 throw 문을 사용하여 예외를 처리하고, 다음 메소드에게도 예외를 던진다.

    다음에 나와 있는 자바 프로그램은 메소드에 대해 throws 선언을 하고, throw 문을 이용하여 실제로 예외 객체를 던지기 위한 예를 보여주는 프로그램입니다.

     class ThrowExceptionTest2 {
       public static void main(String args[]) {
          int a = 10, b = 0, c;
     
          System.out.println("main: begin");
          try {
             System.out.println("main: before divide(a, b)");
             c = divide(a, b);
             System.out.println("main: after divide(a, b)");
          }
          catch(ArithmeticException e) {
             System.out.println("main: catch");
          }
          System.out.println("main: end");
       }
     
       public static int divide(int a, int b)
       throws ArithmeticException {
          int c;
     
          try {
             System.out.println("divide: before a / b");
             return(a / b);
          }
          catch(ArithmeticException e) {
             System.out.println("divide: before throw(e)");
             throw(e);
          }
       }
    }
     
    /*
     * Results:
     
     D:\AIIT\JAVA\Working\Exam\04>java ThrowExceptionTest2
     main: begin
     main: before divide(a, b)
     divide: before a / b
     divide: before throw(e)
     main: catch
     main: end
     
     D:\AIIT\JAVA\Working\Exam\04>
     
     */

    <프로그램 7.ThrowExceptionTest2.java>

    메소드를 재정의할 때 throws 부분은 상속되지 않으므로, throws 문을 포함한 메소드를 재정의할 때는 특별한 규칙이 있습니다.

    • 하위메소드는 상위메소드에서 던지는 예외나 그 하위 클래스의 예외만을 던질 수 있습니다.
    • 예외를 throws하는 메소드가 재정의 될 때, 하위메소드는 throws되는 Exception 그대로나 Exception의 하위클래스 Exception만을 throws 할 수 있습니다.
    • 물론, 아무 것도 throws 하지 않을 수도 있습니다.

     class BaseClass {
       public void myMethod1()
          throws java.io.IOException {
          throw new java.io.IOException();
       }
       public void myMethod2()
          throws java.io.IOException {
          throw new java.io.IOException();
       }
       public void myMethod3()
          throws java.io.IOException {
          throw new java.io.IOException();
       }
    }
     
    class SubClass extends BaseClass {
       public void myMethod1() {
       }
       public void myMethod2()
          throws Exception {    //  (a)
          throw new Exception();
       }
       public void myMethod3()
          throws java.io.IOException {
          throw new java.io.IOException();
       }
    }
     
    class ThrowExceptionTest3 {
       public static void main(String args[]) {
          SubClass sub = new SubClass();
     
          sub.myMethod1();
          try {
             sub.myMethod2();
             sub.myMethod3();
          } catch(java.io.IOException e) {
             System.out.println(e);
          } catch(Exception e) {
             System.out.println(e);
          } finally {
             System.out.println("Finally");
          }
       }
    }
     
    /*
     * Results:
     
     D:\AIIT\JAVA\Working\Exam\04>javac ThrowExceptionTest3.java
     ThrowExceptionTest3.java:19: The method void myMethod2() declared in class
     SubClass cannot override the method of the same signature declared in class
     BaseClass.  Their throws clauses are incompatible.
        public void myMethod2()
                    ^
     1 error
     
     D:\AIIT\JAVA\Working\Exam\04>
      */

    <프로그램 8.ThrowExceptionTest2.java>

    예외를 발생하는 메소드의 재정의는 반드시 상위 클래스에서 throws 하는 예외클래스와 같거나 또는 구 클래스를 상속하는 하위 클래스만을 throws 하거나, 아니면 어떠한 예외도 throws 해서는 안 됩니다. 따라서, (a) 부분에서 ‘throws Exception’ 문을 없애거나 또는 ‘throws java.io.IOException’과 같은 문으로 바꾸어 주어야 합니다.

    예외처리는 실행 중에 많은 시간을 사용하므로, 간단한 테스트 등을 통하여 프로그램을 점검하는 것이 더욱 빠르고 효율적입니다. 예를 들어, 정수형을 입력 받는 메소드라면 예외처리를 하는 것 보다는, 숫자가 정수인지 미리 확인한 후 입력을 하면 예외가 발생하는 것을 막을 수 있겠지요. 예외처리는 문자 그대로 예상 밖의 일을 다루기 위해서 사용하는 것이 우선입니다. 막을 수 있는 예외나 에러 부분에 대해서는 예외처리보다는 다른 방법을 사용하시는 것이 더욱 빠르고 효율적인 수행을 보장하게 됩니다. 또한, 아무런 예외처리를 지정하지 않을 경우 기본 예외처리기가 이를 처리하여 주므로 신뢰성은 감소하지만 더욱 빠른 수행 속도를 얻을 수 있습니다.