读Java编程思想随笔の异常
Java基本理念是"结构不佳的代码不能运行"。
发现错误的理想时机是在编译阶段,也就是在你试图运行程序之前。然而,编译期间并不能找出所有错误,余下的问题必须在运行期间解决。这就需要错误源需要通过某种方式,把适当的信息传递给某个接收者,该接收者将知道如何正确处理这个问题。
改进的错误恢复机制是提供代码健壮性最强有力的方式。错误恢复在我们编写的每一个程序中都是基本要素,但是在Java中它显得格外重要,因为Java的主要目标之一就是创建供他人使用的程序构件。要想创建健壮的系统,它的每一个构件都必须是健壮的。Java使用异常来提供一致的错误报告模型,使得构件能够与客户端代码可靠地沟通问题。
Java中异常处理的目的在于通过使用少于目前数量的代码来简化大型、可靠的程序的生成,并且通过这种方式可以使得你更加自信:你的应用中没有未处理的错误。异常的相关知识学起来并非晦涩难懂,并且它呼吁那种可以使你的项目受益明显、立竿见影的效果。
因为异常处理是Java中唯一正式的错误报告机制,并且通过编译器强制执行。
使用异常所带来的一个明显的好处是,它往往能够降低处理错误代码的复杂度。如果不使用异常,那么就必须检查特定的错误,并在程序中许多地方去处理它。而如果使用异常,那就不必再方法调用处进行检查,因为异常机制将保证能够捕获这个错误。并且,只需在一个地方处理错误,即所谓的异常处理程序中。这种方式不仅节省代码。而且把“描述在正常执行过程中做什么事”的代码和“出了问题怎么办”的代码相分离。总之,与以前错误处理方法相比,异常机制使代码的阅读、编写和调试工作更加井井有条。
基本异常
异常情形是指阻止当前方法或作用域继续执行的问题。把异常情形与普通问题相区分很重要,所谓的普通的问题是指,在当前环境下能得到足够的信息,总能处理这个错误。而对于异常情形,就不能继续下去了,因为在当前环境下无法获得必要的信息来解决问题。你所能做的就是从当前环境跳出,并且把问题提交给上一级环境。这就是抛出异常时所发生的事情。
除法就是个简单地例子。除数可能为0,所以先进行检查很有必要。但是除数为0代表的究竟是什么意思呢?通过当前正在解决的问题环境,或许能知道该如何处理除数为0的情况。但如果这是一个意料之外的值,你也不清楚该如何处理,那就要抛出异常,而不是顺着原来的路径继续执行下去。
当抛出异常后,有几件事会随之发生。首先,同Java中其他对象的创建一样,将使用new在堆上创建异常对象。然后,当前的执行路径被终止,并且从当前环境中弹出对异常对象的引用。此时,异常处理机制接管程序,并开始寻找一个恰当的地方来继续执行程序。这个恰当的地方就是异常处理程序,它的任务是将程序从错误状态中恢复,以使程序要么换一种方式执行要么继续执行下去。
异常参数
与使用Java中的其他对象一样,我们总是用new在堆上创建异常对象,这也伴随着存储空间的分配和构造器的调用。所有标准异常类都有两种构造器:一个是默认构造器;另一个是接受字符串作为参数,以便能把相关信息放入异常对象的构造器:
throw new NullPointerException("t==null");
关键字throw将产生许多有趣的结果。在使用了new创建了异常对象之后,此对象的引用将传给throw。尽管返回的异常对象其类型通常与方法设计的返回类型不同,但从效果上看,它就像是从方法返回的。可以简单的把异常处理看成是一种不同的返回机制,当然若过分强调这种类比的话,就会有点麻烦。另外还能用抛出异常的方式从当前的作用域退出。在这两种情况下,将会返回一个异常对象,然后退出方法或作用域。
此外,能够抛出任意类型的throwable对象,它是异常类型的根类。通常,对于不同类型的错误,要抛出相应的异常。错误信息可以保存在异常对象内部或者用异常类的名称来暗示。上一层环境通过这些信息来知道如何处理这些异常。
捕获异常
要明白异常如何捕获,首先必须了解监控区域的概念。它是一段可能产生异常的代码,并且后面跟着处理这些异常的代码。
try块
如果方法内部抛出了异常(或者在方法内部调用的其他方法抛出了异常),这个方法将在抛出异常的过程中结束。要是不希望方法就此结束,可以在方法内设置一个特殊的块来捕获异常。这个块指的是try块。
注意两点:try中抛出异常代码之后的代码不会执行;跳出try catch finally后的代码会继续执行。
异常处理程序
当然,抛出的异常必须在某处得到处理。这个地点就是异常处理程序,而且针对每个要捕获的异常,得准备相应的处理程序。异常处理程序紧跟在try块之后,以关键字catch表示。
当存在多个catch时,只会进入一个catch,且是就近原则,所以,Exception父类只能在后。
终止与恢复
异常处理理论上有两种处理模型。Java支持终止模型。在这种模型中,将假设错误非常关键,以至于程序无法返回到异常发生的地方继续执行。一旦异常被抛出,就表明错误已无法挽回,也不能回来继续执行。
另一种称为恢复模型。意思是异常处理程序的工作是修正错误,然后重新尝试调用出问题的方法,并任务第二次能成功。对于恢复模型,通常希望异常被处理之后程序会继续执行。
创建自定义异常
不必拘泥于Java中已有的异常类型。Java提供的异常体系不可能预见所有的希望加以报告的错误,所以可以自己定义异常类表示程序中可能会遇到的特定问题。
异常说明
Java鼓励人们把方法可能会抛出的异常告知使用此方法的客户端程序员。这是种优雅的做法,它使得调用者能确切知道写什么样的代码可以捕获所有潜在的异常。当然,如果提供了源代码,客户端程序员可以在源代码中查找throw语句来获取相关信息,然而程序库通常并不与源代码一起发布。为了预防这样的问题,Java提供了相应的语法,使得你能以礼貌的方式告知客户端程序员某个方法可能会抛出的异常类型,然后客户端程序员就可以进行相应的处理。这就是异常说明,它属于方法声明的一部分,紧跟在形式参数列表之后。
异常说明使用了异常关键字throws,后面接一个潜在的异常类型列表。
代码通常必须与异常说明一致。如果方法的代码产生了异常却没有处理,编译器会发现这个问题并提醒你,要么处理这个异常,要么在异常说明中表明此方法将产生异常。通过这种自顶向下强制执行的异常说明机制,Java在编译时就可以保证一定水平的代码正确性。
不过还有个能作弊的地方:可以声明方法将抛出异常,实际上却不抛出。编译器相信了这个声明,并强制此方法的用户像真的抛出异常那样使用这个方法。这样做的好处是,为异常先占个位子,以后就可以抛出这种异常而不用修改已有代码。在定义抽象基类和接口时,这种能力很重要,这样派生类或接口实现就可以抛出这些预先声明的异常。
这种编译时被强制检查的异常称为被检查异常。
printStackTrace()方法所提供的信息可以通过getStackTrace()方法来直接访问,这个方法将返回一个由栈轨迹中的元素所构成的数组,其中每一个元素都表示栈中的一帧。元素0是栈顶元素,并且是调用序列中的最后一个方法调用。数组中的最后一个元素和栈底是调用序列中第一个方法调用。
1 public class WhoCalled { 2 static void f(){ 3 try{ 4 throw new Exception(); 5 }catch(Exception e){ 6 for (StackTraceElement ste:e.getStackTrace()){//栈轨迹元素 7 System.out.println(ste.getClass()); 8 System.out.println(ste.getFileName()); 9 System.out.println(ste.getClassName()); 10 System.out.println(ste.getMethodName()); 11 System.out.println(ste.getLineNumber()); 12 } 13 } 14 } 15 static void g(){f();} 16 static void h(){g();} 17 18 public static void main(String[] args) { 19 f(); 20 System.out.println("++++++++++++++++++++++++++++++++"); 21 //g(); 22 System.out.println("--------------------------------"); 23 // h(); 24 } 25 }
重新抛出异常
有时候希望把刚捕获的异常重新抛出,尤其是在使用Exception捕获所有异常的时候。既然已经得到了对当前异常对象的引用,可以之直接将它抛出:
catch(Exception e){
throw e;
}
如果只是把当前异常对象重新抛出,那么printStackTrace()方法显示的将是原来异常对象抛出点的调用信息,而非重新抛出点的信息。要想更新这个信息,可以调用fillInStackTrace()方法。这将返回一个throwable对象,它是通过把当前调用栈信息填入原来那个异常对象而建立的。
1 public class Rethrowing { 2 public static void f() throws Exception{ 3 System.out.println("originating the excepiton in f()"); 4 throw new Exception("thrown from f()"); 5 } 6 public static void g() throws Exception{ 7 try { 8 f(); 9 } catch (Exception e) { 10 System.out.println("Inside g().e.printStackTrace()"); 11 e.printStackTrace(System.out); 12 throw e; 13 } 14 } 15 public static void h() throws Exception{ 16 try { 17 f(); 18 } catch (Exception e) { 19 System.out.println("Inside h().e.printStackTrace()"); 20 e.printStackTrace(System.out); 21 throw (Exception)e.fillInStackTrace(); 22 //调用e.fillInStackTrace()的那一行就成了异常的新发生地了 23 } 24 } 25 26 public static void main(String[] args) { 27 try { 28 g(); 29 } catch (Exception e) { 30 System.out.println("main:printStackTrace()"); 31 e.printStackTrace(System.out); 32 } 33 try { 34 h(); 35 } catch (Exception e) { 36 System.out.println("main:printStackTrace()"); 37 e.printStackTrace(System.out); 38 } 39 } 40 } 41 // originating the excepiton in f() 42 // Inside g().e.printStackTrace() 43 // java.lang.Exception: thrown from f() 44 // at com.qiqi.exception.Rethrowing.f(Rethrowing.java:13) 45 // at com.qiqi.exception.Rethrowing.g(Rethrowing.java:17) 46 // at com.qiqi.exception.Rethrowing.main(Rethrowing.java:36) 47 // at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 48 // at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) 49 // at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) 50 // at java.lang.reflect.Method.invoke(Method.java:597) 51 // at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140) 52 // main:printStackTrace() 53 // java.lang.Exception: thrown from f() 54 // at com.qiqi.exception.Rethrowing.f(Rethrowing.java:13) 追踪的是原来抛出点的信息 printStackTrace(System.out); 55 // at com.qiqi.exception.Rethrowing.g(Rethrowing.java:17) 56 // at com.qiqi.exception.Rethrowing.main(Rethrowing.java:36) 57 // at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 58 // at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) 59 // at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) 60 // at java.lang.reflect.Method.invoke(Method.java:597) 61 // at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140) 62 // originating the excepiton in f() 63 // Inside h().e.printStackTrace() 64 // java.lang.Exception: thrown from f() 65 // at com.qiqi.exception.Rethrowing.f(Rethrowing.java:13) 66 // at com.qiqi.exception.Rethrowing.h(Rethrowing.java:26) 67 // at com.qiqi.exception.Rethrowing.main(Rethrowing.java:42) 68 // at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 69 // at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) 70 // at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) 71 // at java.lang.reflect.Method.invoke(Method.java:597) 72 // at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140) 73 // main:printStackTrace() 74 // java.lang.Exception: thrown from f() 75 // at com.qiqi.exception.Rethrowing.h(Rethrowing.java:30) 追踪的是重新抛出点的信息 e.fillInStackTrace(); 76 // at com.qiqi.exception.Rethrowing.main(Rethrowing.java:42) 77 // at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 78 // at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) 79 // at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) 80 // at java.lang.reflect.Method.invoke(Method.java:597) 81 // at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)
调用fillInStackTrace()的那一行就成了异常的新发生地了。
有可能在捕获异常之后抛出另一种异常。这么做的话,得到的效果类似于fillInStackTrace(),有关原来异常发生点的信息会丢失,剩下的是与新的抛出点有关的信息。
1 class OneException extends Exception { 2 public OneException(String s) { super(s); } 3 } 4 5 class TwoException extends Exception { 6 public TwoException(String s) { super(s); } 7 } 8 9 public class RethrowNew { 10 public static void f() throws OneException { 11 System.out.println("originating the exception in f()"); 12 throw new OneException("thrown from f()"); 13 } 14 public static void main(String[] args) { 15 try { 16 try { 17 f(); 18 } catch(OneException e) { 19 System.out.println( 20 "Caught in inner try, e.printStackTrace()"); 21 e.printStackTrace(System.out); 22 throw new TwoException("from inner try"); 23 } 24 } catch(TwoException e) { 25 System.out.println( 26 "Caught in outer try, e.printStackTrace()"); 27 e.printStackTrace(System.out); 28 } 29 } 30 } /* Output: 31 originating the exception in f() 32 Caught in inner try, e.printStackTrace() 33 OneException: thrown from f() 34 at RethrowNew.f(RethrowNew.java:15) 35 at RethrowNew.main(RethrowNew.java:20) 36 Caught in outer try, e.printStackTrace() 37 TwoException: from inner try 38 at RethrowNew.main(RethrowNew.java:25) 39 *///:~
最后那个异常仅知道自己来自main(),而对f()一无所知。
永远不必为清理前一个异常对象而担心,或者说为异常对象的清理而担心。它们都是用new在堆上创建的对象,所以垃圾回收器会自动把它们清理掉。
异常链
常常会想要在捕获一个异常后抛出另一个异常,并且希望把原始的异常信息保存下来,这被称为异常链。在JDK1.4以前,程序员必须自己编写代码来保存原始异常信息。现在所有Throwable的子类在构造器中都可以接受一个cause对象作为参数。这个cause就是用来表示原始异常,这样通过把原始异常传递给新的异常,使得即使在当前位置创建并抛出了新的异常,也能通过异常链追踪到异常最初发生的位置。
class DynamicFieldsException extends Exception {} public class DynamicFields { private Object[][] fields; public DynamicFields(int initialSize) { fields = new Object[initialSize][2]; for(int i = 0; i < initialSize; i++) fields[i] = new Object[] { null, null }; } public String toString() { StringBuilder result = new StringBuilder(); for(Object[] obj : fields) { result.append(obj[0]); result.append(": "); result.append(obj[1]); result.append("\n"); } return result.toString(); } private int hasField(String id) { for(int i = 0; i < fields.length; i++) if(id.equals(fields[i][0])) return i; return -1; } private int getFieldNumber(String id) throws NoSuchFieldException { int fieldNum = hasField(id); if(fieldNum == -1) throw new NoSuchFieldException(); return fieldNum; } private int makeField(String id) { for(int i = 0; i < fields.length; i++) if(fields[i][0] == null) { fields[i][0] = id; return i; } // No empty fields. Add one: Object[][] tmp = new Object[fields.length + 1][2]; for(int i = 0; i < fields.length; i++) tmp[i] = fields[i]; for(int i = fields.length; i < tmp.length; i++) tmp[i] = new Object[] { null, null }; fields = tmp; // Recursive call with expanded fields: return makeField(id); } public Object getField(String id) throws NoSuchFieldException { return fields[getFieldNumber(id)][1]; } public Object setField(String id, Object value) throws DynamicFieldsException { if(value == null) { // Most exceptions don't have a "cause" constructor. // In these cases you must use initCause(), // available in all Throwable subclasses. DynamicFieldsException dfe = new DynamicFieldsException(); dfe.initCause(new NullPointerException()); throw dfe; } int fieldNumber = hasField(id); if(fieldNumber == -1) fieldNumber = makeField(id); Object result = null; try { result = getField(id); // Get old value } catch(NoSuchFieldException e) { // Use constructor that takes "cause": throw new RuntimeException(e); } fields[fieldNumber][1] = value; return result; } public static void main(String[] args) { DynamicFields df = new DynamicFields(3); print(df); try { df.setField("d", "A value for d"); df.setField("number", 47); df.setField("number2", 48); print(df); df.setField("d", "A new value for d"); df.setField("number3", 11); print("df: " + df); print("df.getField(\"d\") : " + df.getField("d")); Object field = df.setField("d", null); // Exception } catch(NoSuchFieldException e) { e.printStackTrace(System.out); } catch(DynamicFieldsException e) { e.printStackTrace(System.out); } } } /* Output: null: null null: null null: null d: A value for d number: 47 number2: 48 df: d: A new value for d number: 47 number2: 48 number3: 11 df.getField("d") : A new value for d 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 *///:~
Java标准异常
Throwable这个Java类被用来表示任何可以作为异常被抛出的类。Throwable对象可分为两种类型:Error用来表示编译时或系统错误;Exception是可以被抛出的基本类型,在Java类库,用户方法或运行时故障中都可能抛出Exception异常。
RuntimeException
属于运行时异常的类型有很多,它们会自动被Java虚拟机抛出,所以不必在异常说明中把它们列出来。这些异常都是从RuntimeException继承而来,所以既体现了继承的优点,用起来又方便。这构成了一组具有相同特征和行为的异常类型。并且,也不再需要在异常说明中声明方法将抛出RuntimeException类型的异常,它们被称为不受检查异常。这种异常属于错误,将会自动被捕获,就不用你亲自动手了。要是自己去检查RuntimeException,代码就显得太混乱了。
如果不捕获这种类型的异常会发生什么事呢?因为编译器没有在这个问题上对异常说明进行强制检查,RuntimeException类型的异常也许会穿越所有的执行路径直达main()方法,而不会被捕获。
1 public class NeverCaught { 2 static void f() { 3 throw new RuntimeException("From f()"); 4 } 5 static void g() { 6 f(); 7 } 8 public static void main(String[] args) { 9 g(); 10 } 11 } ///:~
对于RuntimeException这种类型的异常,不需要异常说明,其输出被报告给了System.err。
请务必记住,只能在代码中忽略RuntimeException及其子类型的异常,其他类型异常的处理都是由编译器强制实施的。究其原因,RuntimeException代表的是编程错误:
1、无法预知的错误;
2、作为程序员,应该在代码中进行检查的错误。在一个地方发生异常,常常会在另一个地方导致错误。
使用finally进行清理
对于一些代码,可能会希望无论try块中异常是否抛出,它们都能得到执行。这通常适用于内存回收之外的情况(因为回收由垃圾回收器完成)。为了达到这个效果,可以在异常处理程序后面加上finally子句。
当Java中的异常不允许我们回到异常抛出的地点时,那么该如何应对呢?如果把try块放入循环里,就建立了一个“程序继续执行之前必须要达到”的条件。还可以加入一个static类型的计数器或者别的装置,使循环放弃以前能尝试一次定的次数。
finally用来做什么
对于没有垃圾回收和析构函数自动调用机制的语言来说,finally非常重要。它能使程序员保证:无论try块里发生了什么,内存总能得到释放。但Java有垃圾回收机制,所以内存释放不再是问题。而且,Java也没有析构函数可供调用。那么,Java在什么情况下才能用到finally呢?
当要把除内存之外的资源恢复到它们初始状态时,就要用到finally子句。
1 public class Switch { 2 private static boolean state = false; 3 public boolean read(){return state;} 4 public void on(){ 5 state =true; 6 System.out.println(this); 7 } 8 public void off(){ 9 state =false; 10 System.out.println(this); 11 } 12 @Override 13 public String toString() { 14 return state?"on":"off"; 15 } 16 }
1 public class OnOffException1 extends Exception {
1 public class OnOffException2 extends Exception {}
1 public class OnOffSwitch { 2 private static Switch sw = new Switch(); 3 public static void f() throws OnOffException1,OnOffException2{} 4 public static void main(String[] args) { 5 try { 6 sw.on(); 7 f(); 8 sw.off(); 9 } catch (OnOffException1 onOffException1) { 10 onOffException1.printStackTrace(); 11 System.out.println("OnOffException1"); 12 sw.off(); 13 } catch (OnOffException2 onOffException2) { 14 System.out.println("OnOffException2"); 15 sw.off(); 16 }finally{ 17 System.out.println("over"); 18 } 19 20 } 21 } 22 //on 23 //off
甚至在异常没有被当前的异常处理程序捕获的情况下,异常处理机制也会在跳到更高一层的异常处理程序之前,执行finally子句。
1 public class AlwaysFinally { 2 public static void main(String[] args) { 3 4 try { 5 System.out.println("enter first try block"); 6 try{ 7 throw new FourException(); 8 }finally{ 9 System.out.println("finally in 2st try block"); 10 } 11 } catch (FourException e) { 12 System.out.println("Caught FourException in 1st try block"); 13 //e.printStackTrace(); 14 }finally{ 15 System.out.println("finally in 1st try block"); 16 } 17 } 18 } 19 class FourException extends Exception{} 20 //enter first try block 21 //finally in 2st try block 22 //Caught FourException in 1st try block 23 //finally in 1st try block 24 //程序从上往下执行
在return中使用finally
因为finally子句总是会执行的,所以在一个方法中,可以从多个点返回,并且可以保证重要的清理工作仍旧会执行。
1 public class MultipleReturns { 2 public static void f(int i){ 3 System.out.println("Initialization that requires cleanup"); 4 try{ 5 System.out.println("Point 1"); 6 if(i==1){ 7 return; 8 } 9 }finally { 10 System.out.println("Performing cleanup"); 11 } 12 } 13 14 public static void main(String[] args) { 15 for (int i=1;i<4;i++){ 16 f(i); 17 } 18 } 19 } 20 //Initialization that requires cleanup 21 //Point 1 22 //Performing cleanup 23 //Initialization that requires cleanup 24 //Point 1 25 //Performing cleanup 26 //Initialization that requires cleanup 27 //Point 1 28 //Performing cleanup
异常丢失
遗憾的是,Java异常实现也有瑕疵。异常作为程序出错的标志,决不应该被忽略,但它还是有可能被轻易得忽略。
1 public class LostMessage { 2 void f() throws VeryImportantException{ 3 throw new VeryImportantException(); 4 } 5 void dispose() throws HoHumException{ 6 throw new HoHumException(); 7 } 8 9 public static void main(String[] args) { 10 try{ 11 LostMessage l = new LostMessage(); 12 try{ 13 l.f();//VeryImportantException不见了,被HoHumException取代,由于finally的原因 14 }finally { 15 l.dispose();16 } 17 }catch (Exception e){ 18 System.out.println(e);//这里永远是捕获一次异常,且是最后一个抛出的异常,所以VeryImportantException不见了,被HoHumException取代 19 } 20 } 21 } 22 class VeryImportantException extends Exception{ 23 @Override 24 public String toString() { 25 return "A very important exception"; 26 } 27 } 28 class HoHumException extends Exception{ 29 @Override 30 public String toString() { 31 return "A trivial exception"; 32 } 33 }
异常的限制
当覆盖方法的时候,只能抛出在基类方法异常说明里列出的那些异常。这个限制很有用,因为这意味着,当基类使用的代码应用到其派生类对象时,一样能够工作,异常也不例外。
1 class BaseballException extends Exception {} 2 class Foul extends BaseballException {} 3 class Strike extends BaseballException {} 4 5 abstract class Inning { 6 public Inning() throws BaseballException {} 7 public void event() throws BaseballException { 8 // Doesn't actually have to throw anything 9 } 10 public abstract void atBat() throws Strike, Foul; 11 public void walk() {} // Throws no checked exceptions 12 } 13 14 class StormException extends Exception {} 15 class RainedOut extends StormException {} 16 class PopFoul extends Foul {} 17 18 interface Storm { 19 public void event() throws RainedOut; 20 public void rainHard() throws RainedOut; 21 } 22 23 public class StormyInning extends Inning implements Storm { 24 // OK to add new exceptions for constructors, but you 25 // must deal with the base constructor exceptions: 26 public StormyInning() 27 throws RainedOut, BaseballException {} 28 public StormyInning(String s) 29 throws Foul, BaseballException {} 30 // Regular methods must conform to base class: 31 //! void walk() throws PopFoul {} //Compile error 32 // Interface CANNOT add exceptions to existing 33 // methods from the base class: 34 //! public void event() throws RainedOut {} 35 // If the method doesn't already exist in the 36 // base class, the exception is OK: 37 public void rainHard() throws RainedOut {} 38 // You can choose to not throw any exceptions, 39 // even if the base version does: 40 public void event() {} 41 // Overridden methods can throw inherited exceptions: 42 public void atBat() throws PopFoul {} 43 public static void main(String[] args) { 44 try { 45 StormyInning si = new StormyInning(); 46 si.atBat(); 47 } catch(PopFoul e) { 48 System.out.println("Pop foul"); 49 } catch(RainedOut e) { 50 System.out.println("Rained out"); 51 } catch(BaseballException e) { 52 System.out.println("Generic baseball exception"); 53 } 54 // Strike not thrown in derived version. 55 try { 56 // What happens if you upcast? 57 Inning i = new StormyInning(); 58 i.atBat(); 59 // You must catch the exceptions from the 60 // base-class version of the method: 61 } catch(Strike e) { 62 System.out.println("Strike"); 63 } catch(Foul e) { 64 System.out.println("Foul"); 65 } catch(RainedOut e) { 66 System.out.println("Rained out"); 67 } catch(BaseballException e) { 68 System.out.println("Generic baseball exception"); 69 } 70 } 71 } ///:~
构造器
有一点很重要,即你要时刻询问自己“如果异常发生了,所有东西都能被正确清理吗?”尽管大多数情况下是非常安全的,但涉及构造器时,问题就出现了。构造器会把对象设置成安全的初始状态,但还会有别的动作,比如打开一个文件,这样的动作只有在对象使用完毕并且用户调用了特殊的清理方法之后才能得到清理。如果在构造器内抛出异常,这些清理行为也许就不能正常工作了。
1 public class InputFile { 2 private BufferedReader in; 3 public InputFile(String fname) throws Exception{ 4 try { 5 in = new BufferedReader(new FileReader(fname)); 6 //other code that might throw exceptions 7 }catch (FileNotFoundException e){ 8 //文件还未打开,所以不需关闭文件 9 System.out.println("could not open"+fname); 10 //wasnt open,so dont close it 11 throw e; 12 }catch (Exception e){ 13 //文件已打开,需关闭文件 14 //All other exceptions must close it 15 try { 16 in.close(); 17 }catch (IOException e2){ 18 System.out.println("in.close() unsuccessful"); 19 } 20 throw e;//rethrow 21 }finally { 22 //Dont close it here !!! 23 } 24 } 25 public String getLine(){ 26 String s; 27 28 try { 29 s=in.readLine(); 30 } catch (IOException e) { 31 throw new RuntimeException("readLine() failed"); 32 } 33 return s; 34 } 35 public void dispose(){ 36 try { 37 in.close(); 38 System.out.println("dispose() successful"); 39 } catch (IOException e) { 40 throw new RuntimeException("in.close() unsuccessful"); 41 } 42 } 43 }
对于在构造阶段可能会抛出异常,并且要求清理的类,最安全的使用方式是使用嵌套的try子句:
1 public class Cleanup { 2 public static void main(String[] args) { 3 try { 4 InputFile in = new InputFile("Cleanup.java"); 5 //构造失败,直接进入外围catch语句,不需清理 6 //构造成功,进入finally进行对象清理 7 try{ 8 String s; 9 int i =1; 10 while((s= in.getLine())!=null){ 11 12 } 13 }catch (Exception e){ 14 System.out.println("Caught Exception in main"); 15 e.printStackTrace(System.out); 16 }finally{ 17 in.dispose(); 18 } 19 } catch (Exception e) { 20 System.out.println("InputFile construction failed"); 21 } 22 } 23 }
异常匹配
抛出异常的时候,异常处理系统会按照代码的书写顺序找出最近的处理程序。找到匹配的处理程序之后,它就认为异常将得到处理,然后就不再继续查找。
查找的时候并不要求抛出的异常同处理程序所声明的异常完全匹配。派生类的对象也可以匹配其基类的处理程序。
1 public class Human { 2 public static void main(String[] args) { 3 try{ 4 throw new Sneeze(); 5 }catch (Sneeze S){ 6 System.out.println("Caught Sneeze"); 7 }catch (Annoyance a){ 8 System.out.println("Caught Annoyance"); 9 } 10 try{ 11 throw new Sneeze(); 12 }catch (Annoyance a){ 13 System.out.println("Caught Annoyance"); 14 } 15 } 16 } 17 class Annoyance extends Exception{} 18 class Sneeze extends Annoyance{}
把被检查异常转换为不检查异常
在编写你自己使用的简单程序时,从main()中抛出异常是很简单的,但这不是通用的方法。问题的实质是,当在一个普通方法中调用别的方法时,要考虑到“我不知道该这样处理这个异常”,但是也不想把它吞了,或者打印一些无用的消息。JDK1.4的异常链提供了一种新的思路来解决这个问题。可以直接把“被检查的异常”包装进RuntimeException。
如果想把被检查异常这种功能屏蔽掉的话,这看上去是个好办法。不用吞下异常,也不必把它放到方法的异常说明里面,而异常链还能保证你不会丢失任何原始的异常信息。这种技巧给了你一种选择,你可以不写try-catch子句,直接忽略异常,让它自己沿着调用栈上“冒泡”。同时,还可以用getCause()捕获并处理特定的异常。
1 class WrapCheckedException { 2 void throwRuntimeException(int type) { 3 try { 4 switch(type) { 5 case 0: throw new FileNotFoundException();//被当作基类Exception被捕获 6 case 1: throw new IOException();//被当作基类Exception被捕获 7 case 2: throw new RuntimeException("Where am I?");//被当作基类Exception被捕获 8 default: return; 9 } 10 } catch(Exception e) { // Adapt to unchecked: 11 throw new RuntimeException(e);//不需要在方法的异常说明的列表里声明的,所以通过RuntimeException可以屏蔽掉受检查异常 12 } 13 } 14 } 15 16 class SomeOtherException extends Exception {} 17 18 public class TurnOffChecking { 19 public static void main(String[] args) { 20 WrapCheckedException wce = new WrapCheckedException(); 21 // You can call throwRuntimeException() without a try 22 // block, and let RuntimeExceptions leave the method: 23 wce.throwRuntimeException(3); 24 // Or you can choose to catch exceptions: 25 for(int i = 0; i < 4; i++) 26 try { 27 if(i < 3) 28 wce.throwRuntimeException(i); 29 else 30 throw new SomeOtherException(); 31 } catch(SomeOtherException e) { 32 print("SomeOtherException: " + e); 33 } catch(RuntimeException re) { 34 try { 35 throw re.getCause(); 36 } catch(FileNotFoundException e) { 37 print("FileNotFoundException: " + e); 38 } catch(IOException e) { 39 print("IOException: " + e); 40 } catch(Throwable e) { 41 print("Throwable: " + e); 42 } 43 } 44 } 45 } /* Output: 46 FileNotFoundException: java.io.FileNotFoundException 47 IOException: java.io.IOException 48 Throwable: java.lang.RuntimeException: Where am I? 49 SomeOtherException: SomeOtherException 50 *///:~