第06部分:异常语句

throw语句

异常是一种信号,表明发生了某种异常状况或错误。抛出异常的目的是发出信号,表示有异常状况发生。捕获异常的目的是处理异常,使用必要的操作修复。在 Java 中,throw 语句用于抛出异常:

throw expression;

expression 的计算结果必须是一个异常对象,说明发生了什么异常或错误。后面会详细介绍异常的种类,现在你只需知道,异常通过有点特殊的对象表示。下面是抛出异常的示例代码:

public static double factorial(int x) {

        if (x < 0)

                throw new IllegalArgumentException("x must be >= 0");

        double fact;

        for(fact=1.0; x > 1; fact *= x, x--)

                /* empty */ ;     // 注意,使用的是空语句

        return fact;

}


Java 解释器执行 throw 语句时,会立即停止常规的程序执行,开始寻找能捕获或处理异常的异常处理程序。异常处理程序使用 try/catch/finally 语句编写,后面会介绍。Java解释器先在当前代码块中查找异常处理程序,如果有,解释器会退出这个代码块,开始执行异常处理代码。异常处理程序执行完毕后,解释器会继续执行处理程序后的语句。


如果当前代码块中没有适当的异常处理程序,解释器会在外层代码块中寻找,直到找到为止。如果方法中没有能处理 throw 语句抛出的异常的异常处理程序,解释器会停止运行当前方法,返回调用这个方法的地方,开始在调用方法的代码块中寻找异常处理程序。Java通过这种方式,通过方法的词法结构不断向上冒泡,顺着解释器的调用堆栈一直向上寻找。如果一直没有捕获异常,就会冒泡到程序的 main() 方法。如果在 main() 方法中也没有处理异常,Java 解释器会打印一个错误消息,还会打印一个堆栈跟踪,指明这个异常在哪里发生,然后退出。








try/catch/finally语句

Java 有两种稍微不同的异常处理机制。经典形式是使用 try/catch/finally 语句。这个语句的 try 子句是可能抛出异常的代码块。try 代码块后面是零个或多个 catch 子句,每个子句用于处理特定类型的异常,而且能处理多个不同类型的异常。如果 catch 块要处理多个异常,使用 | 符号分隔各个不同的异常。catch 子句后面是一个可选的 finally 块,包含清理代码,不管 try 块中发生了什么,始终都会执行。


catch 和 finally 子句都是可选的,但每个 try 块都必须有这两个子句中的一个。try、catch 和 finally 块都放在花括号里。花括号是句法必须的一部分,即使子句只包含一个语句也不能省略。


下述代码演示了 try/catch/finally 语句的句法和作用:

try {

        //正常情况下,这里的代码从上到下运行,没有问题

        // 但是,有时可能抛出异常

        // 可能是throw语句直接抛出

        // 也可能是调用的方法间接抛出

}catch (SomeException e1) {

        //这段代码中的语句用于处理SomeException或其子类类型的异常对象

        // 在这段代码中,可以使用名称e1引用那个异常对象

}catch (AnotherException | YetAnotherException e2) {

        // 这段代码中的语句用于处理AnotherException、YetAnotherException

        // 或二者的子类类型的异常。在这段代码中,使用名称e2引用传入的异常对象

}finally {

        //不管try子句的结束方式如何,这段代码中的语句都会执行:

                1)正常结束:到达块的末尾

                2)由break、continue或return语句导致

                3)抛出异常,由上述catch子句处理

                4)抛出异常,未被捕获处理

        //但是,如果在try子句中调用了System.exit(),解释器会立即退出

        //不执行finally子句

}



1. try子句

try 子句的作用很简单,组建一段代码,其中有异常需要处理,或者因某种原因终止执行后需要使用特殊的代码清理。try 子句本身没什么用,异常处理和清理操作在 catch 和finally 子句中进行。


2. catch子句

try 块后面可以跟着零个或多个 catch 子句,指定处理各种异常的代码。每个 catch 子句只有一个参数(可以使用特殊的 | 句法指明 catch 块能处理多种异常类型),指定这个子句能处理的异常类型,以及一个名称,用来引用当前处理的异常对象。catch 块能处理的类型必须是 Throwable 的子类。

有异常抛出时,Java 解释器会寻找一个 catch 子句,它的参数要和异常对象的类型相同,或者是这个类型的子类。解释器会调用它找到的第一个这种 catch 子句。catch 块中的代码应该执行处理异常状况所需的任何操作。假如异常是 java.io.FileNotFoundException,此时或许要请求用户检查拼写,然后重试。

不是所有可能抛出的异常都要有一个 catch 子句处理,有些情况下,正确的处理方式是让异常向上冒泡,由调用方法捕获。还有些情况,例如表示程序错误的NullPointerException 异常,正确的处理方式或许是完全不捕获,随它冒泡,让 Java 解释器退出,打印堆栈跟踪和错误消息。


3. finally子句

finnaly 子句放在 try 子句后面,一般用来执行清理操作(例如关闭文件和网络连接)。finally 子句很有用,因为不管 try 块中的代码以何种方式结束执行,只要有代码执行,finally 子句中的代码就会执行。事实上,只有一种方法能让 try 子句退出而不执行finally 子句——调用 System.exit() 方法,让 Java 解释器停止运行。

正常情况下,执行到 try 块的末尾后会继续执行 finally 块,做必要的清理工作。如果因为 return、continue 或 break 语句而离开 try 块,会先执行 finally 块,然后再转向新的目标代码。

如果 try 块抛出了异常,而且有处理该异常的 catch 块,那么先执行 catch 块,然后在执行 finally 块。如果本地没有能处理该异常的 catch 块,先执行 finally 块,然后再向上冒泡到能处理该异常最近的 catch 子句。

如果 finally 块使用 return、continue、break 或 throw 语句,或者调用的方法抛出了异常,从而转移了控制权,那么待转移的控制权中止,改为执行新的控制权转移。例如,如果 finally 子句抛出了异常,这个异常会取代任何正在抛出的异常。如果 finally 子句使用了 return 语句,就算抛出的异常还没处理,方法也会正常返回。

try 和 finally 子句可以放在一起使用,不处理异常,也没有 catch 子句。此时,finally 块只是负责清理的代码,不管 try 子句中有没有 break、continue 或 return 语句,都会执行。






处理资源的try语句

try 块的标准形式很通用,但有些常见的情况需要开发者小心编写 catch 和 finally 块。这些情况是清理或关闭不再需要使用的资源。

Java(从第 7 版起)提供了一种很有用的机制,能自动关闭需要清理的资源——处理资源的 try 语句(try-with-resources,TWR)。后面会详细介绍 TWR,现在先介绍它的句法。下面的示例展示了如何使用 FileInputStream 类打开文件(得到的对象需要清理):

try (InputStream is = new FileInputStream("/Users/ben/details.txt")) {

                // ......处理这个文件

}

这种新型 try 语句的参数都是需要清理的对象(严格来说,这些对象必须实现 AutoCloseable 接口)。 这些对象的作用域在 try 块中,不管 try块以何种方式退出,都会自动清理。开发者无需编写任何 catch 或 finally 块,Java 编译器会自动插入正确的清理代码。


所有处理资源的新代码都应该使用 TWR 形式编写,因为这种形式比自己动手编写 catch块更少出错,而且不会遇到麻烦的技术问题,例如终结。

你可能感兴趣的:(第06部分:异常语句)