Java标准异常
Throwable这个Java类被用来表示任何可以作为异常被抛出的类。Throwable对象 可分为两种类型(指从Throwable继承而得到的类型): Error用来表示编译时和系统错误(除特殊情况外,一般不用你关心) ; Exception是 可以被抛出的基本类型,在Java类库、用户方法以及运行时故障中都可能拋出Exception型异常。所以Java程序员关心的基类型通常是Exception。要想对异常有全面的了解,最好去浏览一下HTML格式的Java文档( 可以从java.sun.com下载)。为了对不同的异常有个感性的认识,这么做是值得的。但很快你就会发现,这些异常除了名称外其实都差不多。同时,Java中异常的数目在持续增加,所以在书中简单罗列它们毫无意义。所使用的第三方类库也可能会有自己的异常。对异常来说,关键是理解概念以及如何使用。
异常的基本的概念是用名称代表发生的问题,并且异常的名称应该可以望文知意。异常并非全是在java.lang包里定义的;有些异常是用来支持其他像util、net和io这样的程序包,这些异常可以通过它们的完整名称或者从它们的父类中看出端倪。比如,所有的输入/输出异常都是从java.io.IOException继承而来的。
一个特殊的例子:RuntimeException if(t==null){ throw new NullPointerException(); }
如果必须对传递给方法的每个引用都检查其是否为null (因为无法确定调用者是否传入了非法引用),这听起来着实吓人。幸运的是,这不必由你亲自来做,它属于Java的标准运行时检测的一部分。如果对null引用进行调用,Java会 自动抛出NullPointerException异常,所以上述代码是多余的,尽管你也许想要执行其他的检查以确保NullPointerException不会出现。
属于运行时异常的类型有很多,它们会自动被Java虚拟机抛出,所以不必在异常说明中把它们列出来。这些异常都是从RuntimeException类继承而来,所以既体现了继承的优点,使用起来也很方便。这构成了一组具有相同特征和行为的异常类型。并且,也不再需要在异常说明中声明方法将拋出RuntimeException类型的异常(或者任何从RuntimeException继承的异常),它们也被称为“不受检查异常”。这种异常属于错误,将被自动捕获,就不用你亲自动手了。要是自己去检查RuntimeException的话,代码就显得太混乱了。不过尽管通常不用捕获RuntimeException异常,但还是可以在代码中抛出RuntimeException类型的异常。
如果不捕获这种类型的异常会发生什么事呢?因为编译器没有在这个问题上对异常说明进行强制检查,RuntimeException类 型的异常也许会穿越所有的执行路径直达main方法,而不会被捕获。要明白到底发生了什么,可以试试下面的例子:
class NeverCaught{ static void f() { throw new RuntimeException(); } static void g() { f(); } public static void main() { g(); } }
可能读者已经发现,RuntimeException (或任何从它继承的异常)是-一个特例。对于这种异常类型,编译器不需要异常说明,其输出被报告给了System.err:
Exception in thread "main" java.lang.RuntimeException
at throwException.NeverCaught.f(Text.java:16)
at throwException.NeverCaught.g(Text.java:19)
at throwException.NeverCaught.main(Text.java:22)
所以答案是:如果RuntimeException没 有被捕获而直达main(),那么在程序退出前将调用异常的printStackTrace0方法。
请务必记住:只能在代码中忽略RuntimeException (及其子类)类型的异常,其他类型异常的处理都是由编译器强制实施的。究其原因,RuntimeException代表的是编程错误:
1)无法预料的错误。比如从你控制范围之外传递进来的null引用。
2)作为程序员,应该在代码中进行检查的错误。( 比如对于ArrayIndexOutOf-BoundsException,就得注意一下数组的大小了。) 在一一个地方发生的异常,常常会在另一个地方导致错误。
你会发现在这些情况下使用异常很有好处,它们能给调试带来便利。
值得注意的是:不应把Java的异常处理机制当成是单一用途的工具。 是的,它被设计用来处理一些烦人的运行时错误,这些错误往往是由代码控制能力之外的因素导致的;然而,它对于发现某些编译器无法检测到的编程错误,也是非常重要的。
使用finally进行清理
对于一些代码,可能会希望无论try块中的异常是否抛出,它们都能得到执行。这通常适用于内存回收之外的情况(因为回收由垃圾回收器完成)。为了达到这个效果,可以在异常处理程序后面加上finally子句。为了证明finally总能运行,可以试试下面这个程序public class FinallyWorks { static int count=0; public static void main(String[] args) { while(true) { try { if(count++==0) { throw new Exception(); } System.out.println("No Exception"); }catch(Exception e) { System.out.println("Exception"); }finally { System.out.println("In finally"); if(count==3) break; } } } }
可以从输出中发现,无论异常是否被抛出,finally子句总能被执行。
这个程序也给了我们一些思路,当Java中的异 常不允许我们回到异常抛出的地点时,那么该如何应对呢?如果把try块放在循环里,就建立了一个“程序继续执行之前必须要达到”的条件。还可以加入一个static类型的计数器或者别的装置,使循环在放弃以前能尝试-定的次数。这将使程序的健壮性更上一个台阶。 finally用 来做什么
对于没有垃圾回收和析构函数自动调用机制的语言来说,finally非常 重要。它能使程序员保证:无论try块里发生了什么,内存总能得到释放。但Java有 垃圾回收机制,所以内存释放不再是问题。而且,Java也没有析构函数可供调用。那么,Java在什么情况下才能用到finally呢?
当要把除内存之外的资源恢复到它们的初始状态时,就要用到finally子句。这种需要清理的资源包括:已经打开的文件或网络连接,在屏幕上画的图形,甚至可以是外部世界的某个开关,而且在异常没有被当前处理程序捕获的情况下,异常处理机制也会也会跳到更高一层的异常处理程序之前,执行finally子句。最后当涉及break和continue语句的时候,finally 子句也会得到执行。请注意,如果把finally子句和带标签的break及continue配合使用,在Java里就没必要使用goto语句了。
return中使用finally语句
因为finally语句总会执行,所以在一个方法当中,可以多个点返回,并且保证重要的清理工作仍然可以执行
public class Text { public static void f(int i) { try { if(i==1) return; if(i==2) return; if(i==3) return; if(i==4) return; return; }finally { System.out.println("Clean"); } } public static void main(String[] args) { for(int i=1;i<5;i++) { f(i); } } }
输出:
Clean
Clean
Clean
Clean
从输出可以看出,在finally类内部,从何处返回无关紧要
缺憾:异常丢失
遗憾的是,异常处理程序也有瑕疵。异常作为程序出错的标志,绝不应该被忽略,但是它还是可能被轻易忽略。用某些特殊的方式使用finally子句,就会发生这种情况: