|
Java 天地
“异常”指的是程序运行时出现的非正常情况。 在用传统的语言编程时, 程序员只能通过函数的返回值来发出错误信息。 这易于导致很多错误, 因为在很多情况下需要知道错误产生的内部细节。 通常, 用全局变量errno来存储“异常”的类型。 这容易导致误用, 因为一个errno的值有可能在被处理之前被另外的错误覆盖掉。 即使最优美的C语言程序, 为了处理“异常”情况, 也常求助于goto语句。 Java对“异常”的处理是面向对象的。 一个Java的Exception是一个描述“异常”情况的对象。 当出现“异常”情况时, 一个Exception对象就产生了, 并放到产生这个“异常”的成员函数里。 8.1 基础Java的“异常”处理是通过5个关键词来实现的: try, catch, throw, throws和finally。 用try 来执行一段程序, 如果出现“异常”, 系统抛出(throws)一个“异常”, 你可以通过它的类型来捕捉(catch)它, 或最后(finally)由缺省处理器来处理。 下面是“异常”处理程序的基本形式: try { //程 序 块 } catch (ExceptionType1 e) { // 对ExceptionType1的处理 } catch (ExceptionType2 e) { // 对ExceptionType2的处理 throw(e); //再抛出这个“异常” } finally { } 8.2 "异常”的类型在“异常”类层次的最上层有一个单独的类叫做Throwable。 这个类用来表示所有的“异常”情况。 每个“异常”类型都是Throwable的子类。 Throwable有两个直接的子类。 一类是Exception, 是用户程序能够捕捉到的“异常”情况。 我们将通过产生它的子类来创建自己的“异常”。 另一类是Error, 它定义了那些通常无法捕捉到的“异常”。 要谨慎使用Error子类, 因为它们通常会导致灾难性的失败。 在Exception中有一个子类RuntimeException, 它是程序运行时自动地对某些错误作出反应而产生的。 8.3 不捕捉“异常”“异常”对象是Java在运行时对某些“异常”情况作出反应而产生的。 例如, 下面这个小程序包含一个整数被0除的“异常”。 class Exc0 { public static void main(String args[]) { int d = 0; int a = 42/d; } } 当Java执行这个除法时, 由于分母是0, 就会构造一个“异常”对象来使程序停下来并处理这个错误情况, 在运行时“抛出”(throw) 这个“异常”。 说“抛出”是因为它象一个滚烫的马铃薯, 你必须把它抓住并立即处理。 程序流将会在除号操作符处被打断, 然后检查当前的调用堆栈来查找“异常”。 一个“异常”处理器是用来立即处理“异常”情况的。 在这个例子里, 我们没有编一个“异常”处理器, 所以缺省的处理器就发挥作用了。 缺省的处理器打印Exception的字符之值和发生“异常”的地点。 下面是我们的小例子的输出。 C:>java Exc0 8.4 try与catch通常我们希望自己来处理“异常”并继续运行。 可以用try来指定一块预防所有“异常”的程序。 紧跟在try程序后面, 应包含一个catch子句来指定你想要捕捉的“异常”的类型。 例如, 下面的例子是在前面的例子的基础上构造的, 但它包含一个try程序块和一个catch子句。 class exc1 { public static void main(string args[]) { try { int d = 0; int a = 42 / d; } catch (arithmeticexception e) { system.out.println("division by zero"); } } } catch子句的目标是解决“异常”情况, 把一些变量设到合理的状态, 并象没有出错一样继续运行。 如果一个子程序不处理某个“异常”, 则返到上一级处理, 直到最外一级。 8.5 多个catch子句在某些情况下, 同一段程序可能产生不止一种“异常”情况。 你可以放置多个catch子句, 其中每一种“异常”类型都将被检查, 第一个与之匹配的就会被执行。 如果一个类和其子类都有的话, 应把子类放在前面, 否则将永远不会到达子类。 下面是一个有两个catch子句的程序的例子。 class MultiCatch { public static void main(String args[]) { try { int a = args.length; System.out.println("a = " + a); int b = 42/a; int c[] = {1}; c[42] = 99; } catch(ArithmeticException e) { System.out.println("div by 0: " + e); } catch(ArrayIndexOutOfBoundsException e) { system.out.println("array index oob: " + e); } } } 如果在程序运行时不跟参数, 将会引起一个0做除数的“异常”, 因为a的值为0。 如果我们提供一个命令行参数, 将不会产生这个“异常”, 因为a的值大于0。 但会引起一个ArrayIndexOutOfBoundexception的“异常”, 因为整型数组c的长度是1, 却给c[42]赋值。 下面是以上两种情况的运行结果。 C:>java MultiCatch C:>java MutiCatch 1 8.6 try语句的嵌套你可以在一个成员函数调用的外面写一个try语句, 在这个成员函数内部, 写另一个try语句保护其他代码。 每当遇到一个try语句,“异常”的框架就放到堆栈上面, 直到所有的try语句都完成。 如果下一级的try语句没有对某种“异常”进行处理, 堆栈就会展开, 直到遇到有处理这种“异常”的try语句。 下面是一个try语句嵌套的例子。 class MultiNest { static void procedure() { try { int c[] = { 1 }; c[42] = 99; } catch(ArrayIndexOutOfBoundsexception e) { System.out.println("array index oob: " + e); } } public static void main(String args[]) { try { int a = args.length; system.out.println("a = " + a); int b = 42/a; procedure(); } catch(arithmeticException e) { System.out.println("div by 0: " + e); } } } 成员函数procedure里有自己的try/catch控制, 所以main不用去处理ArrayIndexOutOfBoundsException。 8.7 throw语句throw语句用来明确地抛出一个“异常”。 首先, 你必须得到一个Throwable的实例的控制柄, 通过参数传到catch子句, 或者用new操作符来创建一个。 下面是throw语句的通常形式。 throw ThrowableInstance; 程序会在throw语句后立即终止, 它后面的语句执行不到, 然后在包含它的所有try块中从里向外寻找含有与其匹配的catch子句的try块。 下面是一个含有throw语句的例子。 class ThrowDemo { static void demoproc() { try { throw new NullPointerException("de3mo"); } catch(NullPointerException e) { System.out.println("caught inside demoproc"); throw e; } } public static void main(String args[]) { try { demoproc(); } catch(NullPointerException e) { system.out.println("recaught: " + e); } } } 8.8 throws语句throws用来标明一个成员函数可能抛出的各种“异常”。对大多数Exception子类来说, Java 编译器会强迫你声明在一个成员函数中抛出的“异常”的类型。 如果“异常”的类型是Error或 RuntimeException, 或它们的子类, 这个规则不起作用, 因为这些在程序的正常部分中是不期待出现的。 如果你想明确地抛出一个RuntimeException, 你必须用throws语句来声明它的类型。 这就重新定义了成员函数的定义语法: type method-name(arg-list) throws exception-list{ } 下面是一段程序, 它抛出了一个“异常”, 但既没有捕捉它, 也没有用throws来声明。 这在编译时将不会通过。 class ThrowsDemo1 { static void procedure( ) { System.out.println("inside procedure"); throw new IllegalAccessException("demo"); } public static void main(String args[]) { procedure( ); } } 为了让这个例子编译过去, 我们需要声明成员函数procedure抛出了IllegalAccessException, 并且在调用它的成员函数main里捕捉它。 下面是正确的例子: class ThrowsDemo { static void procedure( ) throws IllegalAccessException { System.out.println("inside procedure"); throw new IllegalAccessException("demo"); } public static void main(String args[]) { try { procedure( ); } catch (IllegalAccessException e) { System.out.println("caught " + e); } } } 下面是输出结果: C:>java ThrowsDemo 8.9 finally当一个“异常”被抛出时, 程序的执行就不再是线性的, 跳过一些行, 甚至会由于没有与之匹配的catch子句而过早地返回。 有时确保一段代码不管发生什么“异常”都被执行到是必要的, 关键词finally就是用来标识这样一段代码的。 即使你没有catch子句, finally程序块也会在执行try程序块后的程序之前执行。 每个try语句都需要至少一个与之相配的catch子句或finally子句。 一个成员函数返回到调用它的成员函数, 或者通过一个没捕捉到的“异常”, 或者通过一个明确的return语句, finally子句总是恰好在成员函数返回前执行。 下面是一个例子, 它有几个成员函数, 每个成员函数用不同的途径退出, 但执行了finally子句。 class FinallyDemo { static void procA( ) { try { System.out.println("inside procA"); throw new RuntimeException("demo"); } finally { System.out.println("procA`s finally"); } } static void procB( ) { try { System.out.println("inside procB"); return; } finally { System.out.println("procB`s finally"); } } public static void main(String args[]) { try { procA( ); } catch (Exception e); procB( ); } } 下面是这个例子的运行结果: C:>java FinallyDemo 本章小结1. “异常”指的是程序运行时出现的非正常情况。2. 在“异常”类层次的最上层的类叫Throwable, 它有两个直接的子类: Exception和Error。 3. Java的“异常”处理通过5个关键词来实现: try, catch, throw, throws和finally。 (作者: 刘春阳 来源: 不详) |