Java 异常

异常处理机制

异常处理模型:
终止模型:当异常发生时,就进入异常处理程序,处理结束并不返回异常发生位置继续执行;
恢复模型:该模型认为异常处理的目的在于修正错误,因此错误修正后应该返回错误发生地继续执行。
Java异常处理模型为终止模型,当需要用Java语言实现类似于恢复模型的异常处理流程时,可以采取两种方式:

  • 发生错误时不抛出异常,而是调用另一个错误处理程序进行处理,处理结束后继续执行;
  • 将try-catch语句块置于while循环中,从而可以不断进入try块,直至得到满意的结果。

抛出异常,捕捉异常

异常限制:
异常限制是指当覆盖方法时,子类方法只能抛出在基类方法的异常声明中列出的异常或其子类异常。但该限制对构造器不起作用。子类构造器可以抛出任何异常,不过由于子类初始化以前会先调用基类构造器,所以子类构造器的异常说明中必须包含基类构造器中的异常说明。
一个出现在基类的方法说明中的异常并不一定会出现在派生类的方法说明中。
对于在构造阶段可能会抛出异常并且要求清理的类,最安全的使用方式是使用嵌套的try子句。基本规则为:在创建好一个要清理的对象后就立即进入一个try-finally语句块。

抛出异常:当一个方法出现错误引发异常时,方法创建异常对象并交付运行时系统,异常对象中包含了异常类型和异常出现时的程序状态等异常信息。运行时系统负责寻找处置异常的代码并执行。
** 捕获异常**:在方法抛出异常之后,运行时系统将转为寻找合适的异常处理器(exception handler)。潜在的异常处理器是异常发生时依次存留在调用栈中的方法的集合。当异常处理器所能处理的异常类型与方法抛出的异常类型相符时,即为合适 的异常处理器。运行时系统从发生异常的方法开始,依次回查调用栈中的方法,直至找到含有合适异常处理器的方法并执行。当运行时系统遍历调用栈而未找到合适 的异常处理器,则运行时系统终止。同时,意味着Java程序的终止。

try-catch-finally

try:被try语句块包含的语句被称之为监控区域,try语句块用于在该监控区域中捕获异常,后面可以有0~n个catch语句块,当发生异常时,会从上至下匹配catch异常参数,当发现一个匹配的catch块时,就会执行该块中的语句,执行完则结束try-catch语句,继续执行下面的代码。所以应将子类异常置于父类异常的catch语句之上,以免被父类异常覆盖,而永远得不到执行。当try语句块后面没有catch语句块时,就必须有一个finally语句块与之匹配。
监控区域:一段可能产生异常的代码,后面跟随着异常处理代码
异常说明:方法声明的一部分,紧跟在形式参数之后,使用throws关键字,后面接一个所有潜在异常类型的列表
catch:异常处理程序,catch语句块用于处理try语句块中抛出的异常,这个异常可能来自当前函数,也有可能来自调用的下级函数。
finally:无论异常发生否,finally语句块均会被执行。当在try块或catch块中遇到return语句时,finally语句块将在方法返回之前被执行。当需要把除内存以外的资源恢复到它们的初始状态时,就需要使用到finally子句。这种资源主要有:打开的文件、网络,在屏幕上画的图形等。

finally不会被执行的情况:

  1. 在finally语句块中发生了异常;
  2. 在前面的代码中用了System.exit()退出程序
  3. 程序所在的线程死亡
  4. 关闭CPU

try-catch-finally 顺序:

  1. 当try没有捕获到异常时:try语句块中的语句逐一被执行,程序将跳过catch语句块,执行finally语句块和其后的语句;
  2. 当try捕获到异常,catch语句块里没有处理此异常的情况:当try语句块里的某条语句出现异常时,而没有处理此异常的catch语句块时,此异常将会抛给JVM处理,finally语句块里的语句还是会被执行,但finally语句块后的语句不会被执行;
  3. 当try捕获到异常,catch语句块里有处理此异常的情况:在try语句块中是按照顺序来执行的,当执行到某一条语句出现异常时,程序将跳到catch语句块,并与catch语句块逐一匹配,找到与之对应的处理程序,其他的catch语句块将不会被执行,而try语句块中,出现异常之后的语句也不会被执行,catch语句块执行完后,执行finally语句块里的语句,最后执行finally语句块后的语句;

注:一旦某个catch捕获到匹配的异常类型,将进入异常处理代码。一经处理结束,就意味着整个try-catch语句结束。其他的catch子句不再有匹配和捕获异常类型的机会。
无论异常发生与否,finally语句块都会被执行。

public class TestException {  
    public static void main(String args[]) {  
        int i = 0;  
        String greetings[] = { " Hello world !", " Hello World !! ",  
                " HELLO WORLD !!!" };  
        while (i < 4) {  
            try {  
                // 特别注意循环控制变量i的设计,避免造成无限循环  
                // 如果将下面代码中的greetings[i++]换成
                // greetings[i]; i++;则会引发无限循环
                System.out.println(greetings[i++]);  
            } catch (ArrayIndexOutOfBoundsException e) {  
                System.out.println("数组下标越界异常");  
            } finally {  
                System.out.println("--------------------------");  
            }  
        }  
    }  
}  

抛出异常

Throws抛出异常的规则:

  1. 如果是不可查异常(unchecked exception),即Error、RuntimeException或它们的子类,那么可以不使用throws关键字来声明要抛出的异常,编译仍能顺利通过,但在运行时会被系统抛出。
  2. 必须声明方法可抛出的任何可查异常(checked exception)。即如果一个方法可能出现受可查异常,要么用try-catch语句捕获,要么用throws子句声明将它抛出,否则会导致编译错误
  3. 仅当抛出了异常,该方法的调用者才必须处理或者重新抛出该异常。当方法的调用者无力处理该异常的时候,应该继续抛出,而不是囫囵吞枣。
  4. 调用方法必须遵循任何可查异常的处理和声明规则。若覆盖一个方法,则不能声明与覆盖方法不同的异常。声明的任何异常必须是被覆盖方法所声明异常的同类或子类。

*** 使用throw抛出异常:***
throw总是出现在函数体中,用来抛出一个Throwable类型的异常。程序会在throw语句后立即终止,它后面的语句执行不到,然后在包含它的所有try块中(可能在上层调用函数中)从里向外寻找含有与其匹配的catch子句的try块。
如果抛出了检查异常,则还应该在方法头部声明方法可能抛出的异常类型。该方法的调用者也必须检查处理抛出的异常。

如果所有方法都层层上抛获取的异常,最终JVM会进行处理,处理也很简单,就是打印异常消息和堆栈信息。如果抛出的是Error或RuntimeException,则该方法的调用者可选择处理该异常。

异常链:
经常会希望在捕获一个异常并抛出一个新异常后能够将原有的异常信息保存下来,这种将所有的异常信息均保存下来的机制就是异常链。在Throwable的子类Exception、RuntimeException和Error中均由一个可以接受一个cause(缘由)对象作为参数的构造方法,可以通过这个构造方法将原始异常传递给新异常,使得即使在当前位置创建并抛出了新的异常,也能够通过这个异常链追踪到异常最初发生的位置。而对于这三类异常以外的其余异常类则可以使用initCause()方法将原始异常信息传递给新异常。

package Exception;import util.OutUtil;

public class ExceptionChain {   
    private static class Exception1 extends Exception { }

    public static void main(String[] args) {
        int[] a = new int[3];
        for (int i = 0; i <= a.length; ++i) {
            try {
                if (i >= a.length) {
                    throw new Exception1();
                } 
               a[i] = i * i;
                OutUtil.print(a[i] + "");
            }catch (Exception1 e){
                e.initCause(new ArrayIndexOutOfBoundsException(i));
                e.printStackTrace(System.out);
            }
        }
    }
}
// Output:
0
1
4
Exception.ExceptionChain$Exception1
    at Exception.ExceptionChain.main(ExceptionChain.java:18)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:601)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
Caused by: java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 3
    at Exception.ExceptionChain.main(ExceptionChain.java:23)
    ... 5 more

异常类常用方法:

  • printStackTrace():打印“从方法调用出到异常抛出处”之间的方法调用序列,默认输出到标准错误流
    注意:
  • 将异常和错误信息通过Sys.err输出到标准错误流要比通过Sys.out输出好,因为后者可能会被重定向,前者更容易引起用户的注意
  • 异常可以被重新抛出,重抛异常会将当前这个异常信息抛给上层环境的异常处理程序,同一个try块中的其余catch语句将会被忽略。如果只是简单的将原异常对象重新抛出,那么printStackTrace()方法现实的将是原来异常抛出点的调用栈信息,而并非重新抛出点的信息。但可以通过调用fillInStackTrace()方法更新该信息。如果重新抛出另一个异常信息,那么得到的结果将类似于使用fillInStackTrace()方法,有关原来的异常点信息均会丢失,剩下的是与新的抛出点有关的信息。
    异常丢失:
    虽然异常作为一种程序错误,本不应该被忽略,但是在Java中,异常还是有可能被忽略掉,主要存在于用某些特殊的方式使用finally子句的时候:
  • finally子句中抛出的异常可能会取代try块中抛出的异常,从而导致try块中的异常丢失;
  • 从finally子句返回时,try块中的异常可能会丢失;

你可能感兴趣的:(Java 异常)