>  기사  >  Java  >  Java 예외 모델의 상세 소개 및 분석(그림)

Java 예외 모델의 상세 소개 및 분석(그림)

黄舟
黄舟원래의
2017-03-14 11:27:501738검색


1. 예외의 소개 및 기본

오류를 발견하기 가장 좋은 시기는 컴파일 단계, 즉 프로그램을 실행하기 전입니다. 그러나 컴파일러는 컴파일 중에 모든 오류를 찾을 수 없습니다. 나머지 오류는 런타임 중에만 발견하고 해결할 수 있습니다. 이러한 유형의 오류는 발생 가능합니다. 이를 위해서는 오류 소스가 어떤 방식으로든 적절한 정보를 수신자에게 전달해야 하며, 수신자는 문제를 올바르게 처리하는 방법을 알게 됩니다. 이것이 Java의 오류 보고메커니즘—— 비정상적인 메커니즘입니다. . 이 메커니즘을 통해 프로그램은 정상 실행 중에 수행할 작업에 대한 코드 와 문제가 발생한 경우 수행할 작업에 대한 코드 를 분리할 수 있습니다.
 
  예외 처리 측면에서 Java는 종료 모델을 사용합니다. 이 모델에서는 오류가 너무 심각해서 프로그램이 실행을 계속하기 위해 예외가 발생한 지점으로 돌아갈 수 없다고 가정합니다. 예외가 발생하면 오류가 되돌릴 수 없으며 실행을 계속할 수 없음을 나타냅니다. 종료 모델과 비교하여 또 다른 예외 처리 모델은 예외가 처리된 후에도 프로그램이 계속 실행되도록 하는 복구 모델 입니다. 이 모델은 매력적이지만 주로 이것이 유발하는 결합 때문에 그다지 실용적이지 않습니다. 복구 핸들러는 예외가 발생한 위치를 알아야 하며, 여기에는 발생 위치에 따라 달라지는 일반 코드가 아닌 코드가 반드시 포함되어 있어야 합니다. 코드 작성 및 유지 관리의 어려움.
 
예외 상황에서 예외 발생에는 다음 세 가지가 수반됩니다.

  • 우선 다른 객체와 동일합니다 Java에서 생성과 마찬가지로 예외 개체는 new를 사용하여 힙에 생성됩니다. ,

  • 현재 실행 경로는
  • 종료되고, 예외 개체 에 대한 참조는 현재 환경에서 팝됩니다. 마지막으로

  • 예외 처리 메커니즘이 프로그램
  • 을 인수하고 해당 예외 처리기를 찾기 시작하며 오류 상태에서 프로그램을 복구합니다.

    2. Java 표준 예외
1.
기본 개념

        

예외 클래스 계층 구조 예시

Java 예외 모델의 상세 소개 및 분석(그림)              
- Throwable: 모든 예외 유형의 루트 클래스

Java에서 Throwable은 모든 예외 유형의 루트 클래스입니다.

Throwable에는 ExceptionError라는 두 가지 직접 하위 클래스가 있습니다. 둘 다 Java 예외 처리의 중요한 하위 클래스이며 각각에는 많은 수의 하위 클래스가 포함되어 있습니다. - 오류 : 프로그램 자체에서 처리할 수 없는 오류

 오류는 프로그램에서 처리할 수 없는 오류로, 프로그램 실행에 심각한 문제가 있음을 나타냅니다. 애플리케이션.

이러한 오류의 대부분은 코드 작성자가 수행한 작업과 관련이 없으며 코드가 실행될 때 JVM, 리소스 등과 관련됩니다. 예를 들어 Java Virtual Machine 런타임 오류 (Virtual MachineError)는 JVM에 작업을 계속 실행하는 데 필요한 메모리 리소스가 더 이상 없으면 OutOfMemoryError가 발생합니다. 이러한 예외가 발생하면 JVM(Java Virtual Machine)은 일반적으로 스레드를 종료하도록 선택합니다. 이러한 오류는 확인할 수 없으며 애플리케이션의 제어 및 처리 기능 범위를 벗어납니다. Java에서 오류는 Error의 하위 클래스로 설명됩니다.

예외: 프로그램 자체에서 처리할 수 있는 오류
  • 예외는 일반적으로 Java 프로그래머의 관심사입니다. 이는 Java클래스 라이브러리, 사용자 메서드 및 런타임 오류에서 발생할 수 있습니다. 런타임 예외(RuntimeException에서 파생된 예외) 기타 예외의 두 가지 분기로 구성됩니다. 이 두 가지 예외를 구분하는 규칙은 다음과 같습니다. 프로그램 오류(일반적으로 잘못된 유형 변환, 배열 범위를 벗어난 등의 논리적 오류)으로 인해 발생합니다. 피해야 함) 예외는 RuntimeException에 속합니다. 프로그램 자체에는 문제가 없지만 I/O(예: 존재하지 않는 파일을 열려는 시도)와 같은 오류로 인해 발생한 예외는 다른 예외에 속합니다.


또한 Java 예외(Exception 및 Error 포함)는 일반적으로 확인 예외(checked Exception)로 나눌 수 있습니다. 두 가지 유형이 있습니다: 확인되지 않은 예외.

  • 검사되지 않은 예외: Error 또는 RuntimeException에서 파생된 모든 예외

     검사되지 않은 예외는 컴파일러에서 감지되지 않습니다. 필수 처리가 필요합니다 에는 런타임 예외(RuntimeException 및 해당 하위 클래스) 및 오류(Error)가 포함됩니다. 즉, 프로그램에서 이러한 예외가 발생할 수 있는 경우 try-catch 문으로 포착되지 않거나 throws 절을 사용하여 throw되도록 선언하더라도 컴파일러는 통과합니다.

  • Checked 예외: Unchecked 예외를 제외한 모든 예외

     Checked 예외는 컴파일러 Handled 예외에 필요합니다. 여기에는 두 가지 처리 방법이 있습니다. 예외 포착 및 처리 예외 발생 선언 . 즉, 프로그램에서 그러한 예외가 발생할 수 있는 경우 try-catch 문을 사용하여 이를 포착하거나 throws 절을 사용하여 예외가 발생하도록 선언합니다. 그렇지 않으면 컴파일이 통과되지 않습니다.

  • 지침: 프로그램에서 RuntimeException이 발생하는 경우 프로그래머의 문제임이 틀림없음

  • 예외와 오류의 차이: 예외는 프로그램 자체에서 처리할 수 있지만 오류는 처리할 수 없습니다


3. Java 예외 처리 메커니즘

  1. 예외 처리

    Java 애플리케이션에서 예외 처리 메커니즘은 다음과 같습니다. 예외 발생 그리고 예외를 잡아.
     
     예외 발생: 메소드에서 오류가 발생하여 예외가 발생하면 메소드는 예외 객체를 생성하여 런타임 시스템에 전달합니다. 예외 객체에는 예외 유형이 포함됩니다. 및 예외 발생 시 프로그램 상태 등 예외 정보입니다. 런타임 시스템은 예외를 처리할 코드를 찾아 실행하는 역할을 담당합니다.

     예외 잡기: 메서드에서 예외가 발생한 후 런타임 시스템은 적절한 예외 처리기를 찾습니다(예외 handler). 잠재적 예외 처리기는 예외가 발생할 때 호출 스택에 순서대로 남아 있는 메서드 모음입니다. 예외 핸들러가 처리할 수 있는 예외 유형이 메소드에서 발생한 예외 유형과 일치하면 적합한 예외 핸들러입니다. 런타임 시스템은 예외가 발생한 메소드부터 시작하여 적절한 예외 핸들러가 포함된 메소드를 찾아 실행할 때까지 호출 스택의 메소드를 다시 확인합니다. 런타임 시스템이 적절한 예외 핸들러를 찾지 못한 채 호출 스택을 순회하면 런타임 시스템이 종료됩니다. 동시에 이는 Java 프로그램의 종료를 의미합니다.


런타임 예외, 오류 또는 확인된 예외의 경우 Java 기술에서 요구하는 예외 처리 방법이 다릅니다.

  • 런타임 예외는 선택 해제되어 있으므로 Java는 다음을 규정합니다. 런타임 예외는 Java 런타임 시스템에 의해 자동으로 발생되어 애플리케이션이 런타임 예외를 무시할 수 있습니다.

  • 메소드 실행 중에 발생할 수 있는 오류에 대해 실행 중인 메소드가 이를 포착하고 싶지 않을 때 Java에서는 메소드가 아무 것도 하지 않도록 허용합니다. . 대부분의 오류는 복구할 수 없으며 합리적인 응용 프로그램이

  • 확인된 모든 예외에 대해 포착해서는 안 되는 예외이기 때문입니다. 잡힐 수도 있고 설명을 받을 수도 있습니다. 즉, 메소드가 확인 가능한 예외를 포착하지 않기로 선택한 경우 예외를 발생시키겠다고 선언해야 합니다.

    직접 작성한 코드, Java 개발 환경 패키지의 코드 또는 Java 런타임 시스템과 같은 모든 Java 코드는 예외를 발생시킬 수 있습니다. 누구나 Java의 throw 문을 통해 예외를 던질 수 있습니다.

     일반적으로 Java에서는 확인 가능한 예외를 catch하거나 선언하여 throw하도록 규정하고 있습니다. 확인할 수 없는 RuntimeException 및 오류를 무시할 수 있습니다.


2. 예외 설명

확인된 예외에 대해 Java는 해당 예외를 제공합니다. 클라이언트 프로그래머에게 특정 메서드가 발생할 수 있는 예외 유형을 알려주고 클라이언트 프로그래머가 이에 따라 예외를 처리할 수 있도록 하는 구문입니다. 이는 예외 설명으로, 다음 코드에 표시된 대로 형식 매개변수 목록 바로 뒤에 있는 메서드 선언의 일부입니다.

void f() throws TooBig, TooSmall, pZero { ... }

는 메서드 f를 나타냅니다. 가능한 세 가지 예외인 TooBig, TooSmall 및 pZero가 발생하며

void g() { ... ... }

는 메소드 g가 어떠한 예외도 발생시키지 않음을 의미합니다.

 코드는 예외 설명과 일치해야 합니다. 메서드의 코드가 확인된 예외를 생성했지만 이를 처리하지 않는 경우 컴파일러는 이 문제를 발견하고 사용자에게 다음과 같이 알려줍니다. 예외를 처리하거나 이 메서드가 예외를 생성할 것이라고 예외 설명에 표시합니다. . 그러나 실제로 예외를 발생시키지 않고 메소드가 예외를 발생시키도록 선언할 수 있습니다.


3. 예외 잡기

모니터링 영역: 예외를 발생시킬 수 있는 코드와 이를 처리하는 코드가 이어집니다. try…catch…절 에 표시된 예외가 구현됩니다.

 (1) try 절
 메서드 내부에서 예외가 발생하면 해당 예외를 발생시키는 과정에서 이 메서드가 종료됩니다. 메소드가 여기서 끝나지 않도록 하려면 메소드 내에 특수 블록을 설정하여 예외를 포착할 수 있습니다. 그 중 이 블록에서는 다양한 메소드를 호출하려고 하는 부분을 try 블록이라고 합니다:

try { 
    // Code that might generate exceptions }

  (2) catch 절 – 예외 핸들러
던져진 예외를 처리해야 하고, 각 예외를 포착하려면 해당 예외 처리기를 준비해야 합니다. 예외 핸들러는 catch 키워드로 표시되는 try 블록 뒤에 와야 합니다:

try { 
  // Code that might generate exceptions } catch(Type1 id1)|{ 
  // Handle exceptions of Type1 } catch(Type2 id2) { 
  // Handle exceptions of Type2 } catch(Type3 id3) { 
  // Handle exceptions of Type3 }

예외 핸들러는 식별자(id1, id2, ...)를 사용할 수 없습니다. , 예외 유형 자체가 이미 예외를 처리하는 데 충분한 정보를 제공하지만 식별자가 생략되지 않기 때문입니다. 예외가 발생하면 예외 처리 메커니즘은 매개변수가 예외 유형과 일치하는 첫 번째 처리기를 검색합니다. 그러면 해당 캐치를 입력하고 자동으로 실행하게 되는데 이때 예외가 처리된 것으로 간주됩니다. catch 절이 끝나면 처리기의 조회가 종료됩니다(switch…case…와는 다름).
 
다음 사항에 특별한 주의를 기울여야 합니다.

  • 예외 일치 원칙: 예외가 발생하는 경우, 예외 처리 시스템은 코드가 작성된 순서대로 가장 일치하는 핸들러(파생 클래스의 객체가 기본 클래스의 핸들러와 일치할 수 있음)를 찾습니다. 일단 발견되면 예외가 처리되고 검색을 중지한다고 가정합니다.

  • 마스크할 수 없는 파생 클래스 예외:

    Catch 기본 클래스 예외 절은 파생 클래스의 예외를 캡처하는 catch 절 뒤에 배치되어야 합니다. 그렇지 않으면 컴파일이 통과되지 않습니다.

  • catch 절은 try 절과 함께 사용해야 합니다.

    .

  •  (3)
final
ly절

finally 블록 설명

      The finally block always executes when the try block exits. This ensures that the finally block is executed even if an unexpected exception occurs. But finally is useful for more than just exception handling — it allows the programmer to avoid having cleanup code accidentally bypassed by a return,continue, or break. Putting cleanup code in a finally block is always a good practice, even when no exceptions are anticipated.
      
      Note: If the JVM exits while the try or catch code is being executed, then the finally block may not execute. Likewise, if the thread executing the try or catch code is interrupted or killed, the finally block may not execute even though the application as a whole continues.


  • finally 子句 总会被执行(前提:对应的 try子句 执行)
     
     下面代码就没有执行 finally 子句:

 public class Test { 
    public static void main(String[] args) { 
        System.out.println("return value of test(): " + test()); 
    } 

    public static int test() { 
        int i = 1; 
        System.out.println("the previous statement of try block"); 
        i = i / 0; 

        try { 
            System.out.println("try block"); 
            return i; 
        }finally { 
            System.out.println("finally block"); 
        } 
     } 
 }/* Output:
        the previous statement of try block 
        Exception in thread "main" java.lang.ArithmeticException: / by zero 
        at com.bj.charlie.Test.test(Test.java:15) 
        at com.bj.charlie.Test.main(Test.java:6)
 *///:~

  当代码抛出一个异常时,就会终止方法中剩余代码的执行,同时退出该方法的执行。如果该方法获得了一些本地资源,并且这些资源(eg:已经打开的文件或者网络连接等)在退出方法之前必须被回收,那么就会产生资源回收问题。这时,就会用到finally子句,示例如下:

InputStream in = new FileInputStream(...);try{
    ...
}catch (IOException e){
    ...
}finally{
    in.close();
}

  • finally 子句与控制转移语句的执行顺序

      A finally clause can also be used to clean up for break, continue and return, which is one reason you will sometimes see a try clause with no catch clauses. When any control transfer statement is executed, all relevant finally clauses are executed. There is no way to leave a try block without executing its finally clause.

      先看四段代码:

// 代码片段1
 public class Test { 
    public static void main(String[] args) {  
        try {  
            System.out.println("try block");  
            return ;  
        } finally {  
            System.out.println("finally block");  
        }  
    }  
 }/* Output:
        try block 
        finally block
 *///:~
// 代码片段2public class Test { 
    public static void main(String[] args) {  
        System.out.println("reture value of test() : " + test()); 
    } 

    public static int test(){ 
        int i = 1; 

        try {  
            System.out.println("try block");  
            i = 1 / 0; 
            return 1;  
        }catch (Exception e){ 
            System.out.println("exception block"); 
            return 2; 
        }finally {  
            System.out.println("finally block");  
        } 
    } 
}/* Output:
        try block 
        exception block 
        finally block 
        reture value of test() : 2
 *///:~
// 代码片段3public class ExceptionSilencer { 
    public static void main(String[] args) { 
        try { 
            throw new RuntimeException(); 
        } finally { 
            // Using ‘return’ inside the finally block 
            // will silence any thrown exception. 
            return; 
        } 
    } 
} ///:~
// 代码片段4class VeryImportantException extends Exception { 
    public String toString() {return "A very important exception!"; } 
} 

class HoHumException extends Exception { 
    public String toString() { 
        return "A trivial exception"; 
    } 
} 

public class LostMessage { 
    void f() throws VeryImportantException { 
        throw new VeryImportantException(); 
    } 
    void dispose() throws HoHumException { 
        throw new HoHumException(); 
    } 
    public static void main(String[] args) { 
        try { 
            LostMessage lm = new LostMessage(); 
            try { 
                lm.f(); 
            } finally { 
                lm.dispose(); 
            } 
        } catch(Exception e) { 
        System.out.println(e); 
        } 
    } 
} /* Output: 
   A trivial exception 
*///:~

  从上面的四个代码片段,我们可以看出,finally子句 是在 try 或者 catch 中的 return 语句之前执行的。更加一般的说法是,finally子句 应该是在控制转移语句之前执行,控制转移语句除了 return 外,还有 break 和 continue。另外,throw 语句也属于控制转移语句。虽然 return、throw、break 和 continue 都是控制转移语句,但是它们之间是有区别的。其中 return 和 throw 把程序控制权转交给它们的调用者(invoker),而 break 和 continue 的控制权是在当前方法内转移。


  下面,再看两个代码片段:

// 代码片段5public class Test { 
    public static void main(String[] args) { 
        System.out.println("return value of getValue(): " + getValue()); 
    } 

    public static int getValue() { 
        try { 
                return 0; 
        } finally { 
                return 1; 
            } 
     } 
 }/* Output:
        return value of getValue(): 1
 *///:~
// 代码片段6public class Test { 
    public static void main(String[] args) { 
        System.out.println("return value of getValue(): " + getValue()); 
    } 

    public static int getValue() { 
        int i = 1; 
        try { 
                return i; 
        } finally { 
                i++; 
        } 
    } 
 }/* Output:
         return value of getValue(): 1
 *///:~

  利用我们上面分析得出的结论:finally子句 是在 try子句 或者 catch子句 中的 return 语句之前执行的。 由此,可以轻松的理解代码片段 5 的执行结果是 1。因为 finally 中的 return 1;语句要在 try 中的 return 0;语句之前执行,那么 finally 中的 return 1;语句执行后,把程序的控制权转交给了它的调用者 main()函数,并且返回值为 1。
  
  那为什么代码片段 6 的返回值不是 2,而是 1 呢? 按照代码片段 5 的分析逻辑,finally 中的 i++;语句应该在 try 中的 return i;之前执行啊? i 的初始值为 1,那么执行 i++;之后为 2,再执行 return i;那不就应该是 2 吗?怎么变成 1 了呢?
  
  关于 Java 虚拟机是如何编译 finally 子句的问题,有兴趣的读者可以参考《 The JavaTM Virtual Machine Specification, Second Edition 》中 7.13 节 Compiling finally。那里详细介绍了 Java 虚拟机是如何编译 finally 子句。实际上,Java 虚拟机会把 finally 子句作为 subroutine 直接插入到 try 子句或者 catch 子句的控制转移语句之前。但是,还有另外一个不可忽视的因素,那就是在执行 subroutine(也就是 finally 子句)之前,try 或者 catch 子句会保留其返回值到本地变量表(Local Variable Table)中。待 subroutine 执行完毕之后,再恢复保留的返回值到操作数栈中,然后通过 return 或者 throw 语句将其返回给该方法的调用者(invoker)。

  请注意,前文中我们曾经提到过 return、throw 和 break、continue 的区别,对于这条规则(保留返回值),只适用于 return 和 throw 语句,不适用于 break 和 continue 语句,因为它们根本就没有返回值。

  下面再看最后三个代码片段:

// 代码片段7public class Test { 
    public static void main(String[] args) { 
        System.out.println("return value of getValue(): " + getValue()); 
    } 

@SuppressWarnings("finally") 
public static int getValue() { 
    int i = 1; 
    try { 
            i = 4; 
        } finally { 
            i++; 
            return i; 
        } 
    } 
 }/* Output:
        return value of getValue(): 5
 *///:~
// 代码片段8public class Test { 
    public static void main(String[] args) { 
        System.out.println("return value of getValue(): " + getValue()); 
    } 

 public static int getValue() { 
    int i = 1; 
    try { 
            i = 4; 
        } finally { 
            i++; 
        } 
        return i; 
     } 
 }/* Output:
        return value of getValue(): 5
 *///:~
// 代码片段9public class Test { 
    public static void main(String[] args) {  
        System.out.println(test());  
    }  

    public static String test() {  
        try {  
            System.out.println("try block");  
            return test1();  
        } finally {  
            System.out.println("finally block");  
        }  
    }  
    public static String test1() {  
        System.out.println("return statement");  
        return "after return";  
    }  
}/* Output:
        try block 
        return statement 
        finally block 
        after return
 *///:~

  请注意,最后个案例的唯一一个需要注意的地方就是,return test1(); 这条语句等同于 :

 String tmp = test1(); 
 return tmp;

  因而会产生上述输出。


  特别需要注意的是,在以下4种特殊情况下,finally子句不会被(完全)执行:
  
  1)在 finally 语句块中发生了异常;
  2)在前面的代码中用了 System.exit()【JVM虚拟机停止】退出程序;
  3)程序所在的线程死亡;
  4)关闭 CPU;


四. 异常的限制

 
  当覆盖方法时,只能抛出在基类方法的异常说明里列出的那些异常。这意味着,当基类使用的代码应用到其派生类对象时,一样能够工作。
  

class BaseballException extends Exception {} 
class Foul extends BaseballException {} 
class Strike extends BaseballException {} 

abstract class Inning { 
    public Inning() throws BaseballException {} 
    public void event() throws BaseballException { 
        // Doesn’t actually have to throw anything 
    } 
    public abstract void atBat() throws Strike, Foul; 
    public void walk() {} // Throws no checked exceptions } 

class StormException extends Exception {} 
class RainedOut extends StormException {} 
class PopFoul extends Foul {} 

interface Storm { 
    public void event() throws RainedOut; 
    public void rainHard() throws RainedOut; 
} 

public class StormyInning extends Inning implements Storm { 
    // OK to add new exceptions for constructors, but you must deal with the base constructor exceptions: 
    public StormyInning() throws RainedOut, BaseballException {} 

    public StormyInning(String s) throws Foul, BaseballException {} 

    // Regular methods must conform to base class: 
    void walk() throws PopFoul {}   //Compile error 

    // Interface CANNOT add exceptions to existing methods from the base class: 
    public void event() throws RainedOut {} 

    // If the method doesn’t already exist in the base class, the exception is OK: 
    public void rainHard() throws RainedOut {} 

    // You can choose to not throw any exceptions, even if the base version does: 
    public void event() {} 

    // Overridden methods can throw inherited exceptions: 
    public void atBat() throws PopFoul {} 

    public static void main(String[] args) { 
        try { 
            StormyInning si = new StormyInning(); 
            si.atBat(); 
        } catch(PopFoul e) { 
            System.out.println("Pop foul"); 
        } catch(RainedOut e) { 
            System.out.println("Rained out"); 
        } catch(BaseballException e) { 
            System.out.println("Generic baseball exception"); 
        } 

        // Strike not thrown in derived version. 
        try { 

            // What happens if you upcast? ----印证“编译器的类型检查是静态的,是针对引用的!!!”
            Inning i = new StormyInning(); 
            i.atBat(); 
            // You must catch the exceptions from the base-class version of the method: 
        } catch(Strike e) { 
            System.out.println("Strike"); 
        } catch(Foul e) { 
            System.out.println("Foul"); 
        } catch(RainedOut e) { 
            System.out.println("Rained out"); 
        } catch(BaseballException e) { 
            System.out.println("Generic baseball exception"); 
        } 
    } 
} ///:~
  • 异常限制对构造器不起作用

      子类构造器不必理会基类构造器所抛出的异常。然而,因为基类构造器必须以这样或那样的方式被调用(这里默认构造器将自动被调用),派生类构造器的异常说明必须包含基类构造器的异常说明。

  • 派生类构造器不能捕获基类构造器抛出的异常

      因为 super() 必须位于子类构造器的第一行,而若要捕获父类构造器的异常的话,则第一行必须是 try 子句,这样会导致编译不会通过。

  • 派生类所重写的方法抛出的异常列表不能大于父类该方法的异常列表,即前者必须是后者的子集

      通过强制派生类遵守基类方法的异常说明,对象的可替换性得到了保证。需要指出的是,派生类方法可以不抛出任何异常,即使基类中对应方法具有异常说明。也就是说,一个出现在基类方法的异常说明中的异常,不一定会出现在派生类方法的异常说明里。

  • 异常说明不是方法签名的一部分

      尽管在继承过程中,编译器会对异常说明做强制要求,但异常说明本身并不属于方法类型的一部分,方法类型是由方法的名字及其参数列表组成。因此,不能基于异常说明来重载方法。


五. 自定义异常

  使用Java内置的异常类可以描述在编程时出现的大部分异常情况。除此之外,用户还可以自定义异常。用户自定义异常类,只需继承Exception类即可。
  
  在程序中使用自定义异常类,大体可分为以下几个步骤:

  (1)创建自定义异常类;
  (2)在方法中通过throw关键字抛出异常对象;
  (3)如果在当前抛出异常的方法中处理异常,可以使用try-catch语句捕获并处理;否则在方法的声明处通过throws关键字指明要抛出给方法调用者的异常,继续进行下一步操作;
  (4)在出现异常方法的调用者中捕获并处理异常。


六. 异常栈与异常链

1、栈轨迹

  printStackTrace() 方法可以打印Throwable和Throwable的调用栈轨迹。调用栈显示了由异常抛出点向外扩散的所经过的所有方法,即方法调用序列(main方法 通常是方法调用序列中的最后一个)。


2、重新抛出异常

catch(Exception e) { 
System.out.println("An exception was thrown"); 
throw e; 
}

  既然已经得到了对当前异常对象的引用,那么我们就可以像上面一样将其重新抛出。重新抛出的异常会把异常抛给上一级环境中的异常处理程序,同一个try子句的后续catch子句将被忽略。此外,如果只是把当前异常对象重新抛出,那么printStackTrace() 方法显示的仍是原来异常抛出点的调用栈信息,而并非重新抛出点的信息。要想更新这个信息,可以调用fillInStackTrace() 方法,这将返回一个Throwable对象,它是通过把当前调用栈信息填入原来那个异常对象而建立的。
  
看下面示例:

public class Rethrowing { 
    public static void f() throws Exception { 
        System.out.println("originating the exception in f()"); 
        throw new Exception("thrown from f()"); 
    } 

    public static void g() throws Exception { 
        try { 
            f(); 
        } catch(Exception e) { 
            System.out.println("Inside g(),e.printStackTrace()"); 
            e.printStackTrace(System.out); 
            throw e; 
        } 
    } 

    public static void h() throws Exception {        try { 
            f(); 
        } catch(Exception e) { 
            System.out.println("Inside h(),e.printStackTrace()"); 
            e.printStackTrace(System.out); 
            throw (Exception)e.fillInStackTrace(); 
        } 
    } 

    public static void main(String[] args) { 
        try { 
            g(); 
        } catch(Exception e) { 
            System.out.println("main: printStackTrace()"); 
            e.printStackTrace(System.out); 
        } 
        try { 
            h(); 
        } catch(Exception e) { 
            System.out.println("main: printStackTrace()"); 
            e.printStackTrace(System.out); 
        } 
    } 
} /* Output: 
    originating the exception in f() 
    Inside g(),e.printStackTrace() 
    java.lang.Exception: thrown from f() 
    at Rethrowing.f(Rethrowing.java:7) 
    at Rethrowing.g(Rethrowing.java:11) 
    at Rethrowing.main(Rethrowing.java:29) 
    main: printStackTrace() 
    java.lang.Exception: thrown from f() 
    at Rethrowing.f(Rethrowing.java:7) 
    at Rethrowing.g(Rethrowing.java:11) 
    at Rethrowing.main(Rethrowing.java:29) 
    originating the exception in f() 
    Inside h(),e.printStackTrace() 
    java.lang.Exception: thrown from f() 
    at Rethrowing.f(Rethrowing.java:7) 
    at Rethrowing.h(Rethrowing.java:20) 
    at Rethrowing.main(Rethrowing.java:35) 
    main: printStackTrace() 
    java.lang.Exception: thrown from f() 
    at Rethrowing.h(Rethrowing.java:24) 
    at Rethrowing.main(Rethrowing.java:35) 
*///:~

3、异常链

 异常链:在捕获一个异常后抛出另一个异常,并且希望把原始异常的信息保存下来。
 
  这可以使用带有cause参数的构造器(在Throwable的子类中,只有Error,Exception和RuntimeException三个类提供了带有cause的构造器)或者使用initcause()方法把原始异常传递给新的异常,使得即使在当前位置创建并抛出了新的异常,也能通过这个异常链追踪到异常最初发生的位置,例如:

class DynamicFieldsException extends Exception {}

...

DynamicFieldsException dfe = new DynamicFieldsException(); 
dfe.initCause(new NullPointerException()); 
throw dfe;

...//捕获该异常并打印其调用站轨迹为:/**
DynamicFieldsException 
at DynamicFields.setField(DynamicFields.java:64) 
at DynamicFields.main(DynamicFields.java:94) 
Caused by: java.lang.NullPointerException 
at DynamicFields.setField(DynamicFields.java:66) 
... 1 more
*/

以 RuntimeException 及其子类NullPointerException为例,其源码分别为:
  
  RuntimeException 源码包含四个构造器,有两个可接受cause:

public class RuntimeException extends Exception {
    static final long serialVersionUID = -7034897190745766939L;    
    /** Constructs a new runtime exception with <code>null</code> as its
     * detail message.  The cause is not initialized, and may subsequently be
     * initialized by a call to {@link #initCause}.
     */
    public RuntimeException() {    super();
    }    /** Constructs a new runtime exception with the specified detail message.
     * The cause is not initialized, and may subsequently be initialized by a
     * call to {@link #initCause}.
     *
     * @param   message   the detail message. The detail message is saved for 
     *          later retrieval by the {@link #getMessage()} method.
     */
    public RuntimeException(String message) {    super(message);
    }    /**
     * Constructs a new runtime exception with the specified detail message and
     * cause.  <p>Note that the detail message associated with
     * <code>cause</code> is <i>not</i> automatically incorporated in
     * this runtime exception&#39;s detail message.
     *
     * @param  message the detail message (which is saved for later retrieval
     *         by the {@link #getMessage()} method).
     * @param  cause the cause (which is saved for later retrieval by the
     *         {@link #getCause()} method).  (A <tt>null</tt> value is
     *         permitted, and indicates that the cause is nonexistent or
     *         unknown.)
     * @since  1.4
     */
    public RuntimeException(String message, Throwable cause) {        super(message, cause);
    }    /** Constructs a new runtime exception with the specified cause and a
     * detail message of <tt>(cause==null ? null : cause.toString())</tt>
     * (which typically contains the class and detail message of
     * <tt>cause</tt>).  This constructor is useful for runtime exceptions
     * that are little more than wrappers for other throwables.
     *
     * @param  cause the cause (which is saved for later retrieval by the
     *         {@link #getCause()} method).  (A <tt>null</tt> value is
     *         permitted, and indicates that the cause is nonexistent or
     *         unknown.)
     * @since  1.4
     */
    public RuntimeException(Throwable cause) {        super(cause);
    }
}

  NullPointerException 源码仅包含两个构造器,均不可接受cause:

public class NullPointerException extends RuntimeException {
    /**
     * Constructs a <code>NullPointerException</code> with no detail message.
     */
    public NullPointerException() {    super();
    }    /**
     * Constructs a <code>NullPointerException</code> with the specified 
     * detail message. 
     *
     * @param   s   the detail message.
     */
    public NullPointerException(String s) {    super(s);
    }
}

注意:

  所有的标准异常类都有两个构造器:一个是默认构造器;另一个是接受字符串作为异常说明信息的构造器。


위 내용은 Java 예외 모델의 상세 소개 및 분석(그림)의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.