第12章 通过异常处理错误
第12章 通过异常处理错误
java的基本理念是”结构不佳的代码不能运行“
通过这种方式可以使得你更加自信:你的应用中没有未处理的错误。异常处理时Java中唯一正式的错误报告机制,并且通过编译器强制执行。
12.1 概念
把”描述再正常执行过程中做什么事“的代码和”出了问题怎么办的代码相分离“异常使代码相分离。
12.2 基本概念
当抛出异常后
- 使用new再堆上创建异常对象
- 当前执行路径(他不能继续下去)被终止,并且从当前环境中弹出对异常对象的引用
- 异常处理机制接管程序
- 寻找一个恰当的地方(异常处理程序)继续执行程序
- 将程序从错误状态中恢复,以使程序要么换一种方式运行,要么继续运行下去
12.2.1 异常参数
用new再堆上创建异常对象,伴随着存储空间的分配和构造器的调用。所有标准异常类都有两个构造器:
- 一个是默认构造器
- 另一个接受字符串为参数,以便能把相关信息放入异常对象的构造器
Throw new NullPointerException("t = null");
new创建了异常对象之后,此对象的引用将传给throw。尽管返回的异常对象其类型通常与方法设计的返回类型不同,但从效果上看,它就像使从方法”返回“的。
12.3 捕获异常
监控区域(guarded region)的概念
12.3.1 try块
方法内部抛出异常(方法内部调用其他的方法抛出了异常)
12.3.1 异常处理程序
紧跟在try后,以关键字catch表示(看起来就像是接受一个且仅接受一个特殊类型的方法 )
-
当异常被抛出时,异常处理机制将负责搜寻参数与异常类型相匹配的第一个处理程序
-
然后进入catch子句执行,此时认为异常得到了处理
-
catch子句结束,处理程序的查找过程结束
-
只有匹配的catch才能执行
终止与恢复
异常处理理论上有两种基本模型。
- Java支持终止模型(它是Java和C++所支持的模型)。在这种模型中,一旦异常被抛出,就表明错误已经无法挽回,也不能回来继续执行。
- 恢复模型。异常处理程序的工作是修正错误,然后重新尝试调用出问题的方法,并认为第二次能成功。对于恢复模型,通常希望异常被处理之后能继续执行程序。
- 如果Java像实现类似恢复的行为,那么在遇见错误时就不能抛出异常,而是调用方法来修正该错误,或者把try放进while循环里,这样就不断地进入try块,直到得到满意的结果。
- 恢复性异常 导致耦合,恢复性程序需要了解异常抛出的地点 , 这势必要包含依赖于抛出位置的非通用性代码。
12.4 创建自定义异常
自己定义异常类来表示程序中可能遇到的特定问题,最好是选择意思相近的异常类继承。
练习1
/* Create a class with a main(0 that throws an object of class Exception * inside a try block. Give the constructor for Exception a String argument. * Catch the exception inside a catch clause and print the String argument. * Add a finally clause and print a message to prove you were there. */ class Exception1 extends Exception { public Exception1(String msg) { super(msg);//super构造基类构造器 System.out.println("Exception1(String msg)"); } } public class No1Ex { public static void f() throws Exception1 { System.out.println("Throwing MyException from f()"); throw new Exception1("From f()"); } public static void main(String[] args) { try { f(); } catch(Exception1 e) { System.err.println("Caught Exception1"); e.printStackTrace(); } finally { System.out.println("Made it to finally"); } } } ============================================================= Throwing MyException from f() Exception1(String msg) Made it to finally Caught Exception1 第十二章异常处理.Exception1: From f() at 第十二章异常处理.No1Ex.f(No1Ex.java:18) at 第十二章异常处理.No1Ex.main(No1Ex.java:22)
练习2
/* Define an object reference and initialize it to null. Try to call a method * through this reference. Now wrap the code in a try-catch clause to catch the * exception. */ public class No2Ex { private static Integer i = null; public static void main(String[] args) { // leads to NullPointerException: // System.out.println(i.toString()); try { System.out.println(i.toString()); } catch(NullPointerException e) { System.err.println("Caught NullPointerException"); e.printStackTrace(); } try { i = 10; System.out.println(i.toString()); } catch(NullPointerException e) { System.err.println("Caught NullPointerException"); e.printStackTrace(); } finally { System.out.println("Got through it"); } } } ======================================================================= Caught NullPointerException java.lang.NullPointerException at 第十二章异常处理.No2Ex.main(No2Ex.java:14) 10 Got through it
练习3
package 第十二章异常处理; // Write code to generate and catch an ArrayIndexOutOfBoundsException. public class No3Ex { private static int[] ia = new int[2]; public static void main(String[] args) { try { ia[2] = 3; } catch(ArrayIndexOutOfBoundsException e) { System.err.println( "Caught ArrayIndexOutOfBoundsException"); e.printStackTrace(); } } } ========================================================================= Caught ArrayIndexOutOfBoundsException java.lang.ArrayIndexOutOfBoundsException: 2 at 第十二章异常处理.No3Ex.main(No3Ex.java:8)
练习4
/* Create your own exception class using the extends keyword. Write a * constructor for this class that takes a String argument and stores it inside * the object with a String reference. Write a method that displays the stored * String. Create a try-catch clause to exercise your new exception. */ class Exception4 extends Exception { private String msg; Exception4(String msg) { super(msg); System.out.println("Exception4()"); this.msg = msg; } protected void showS() { System.out.println("Message from Exception4: " + msg); } } public class No4Ex { public static void f() throws Exception4 { System.out.println("f()"); throw new Exception4("Ouch from f()"); } public static void main(String[] args) { try { f(); } catch(Exception4 e) { System.err.println("Caught Exception4"); e.printStackTrace(); e.showS(); } } } =============================================================f() Exception4() Message from Exception4: Ouch from f() Caught Exception4 第十二章异常处理.Exception4: Ouch from f() at 第十二章异常处理.No4Ex.f(No4Ex.java:24) at 第十二章异常处理.No4Ex.main(No4Ex.java:28)
练习5
/* Create you own resumption-like behavior using a while loop that repeats * until an exception is no longer thrown. */ public class No5Ex { private static int[] ia = new int[2]; static int x = 5; public static void main(String[] args) { while(true) { try { ia[x] = 1; System.out.println(ia[x]); break; } catch(ArrayIndexOutOfBoundsException e) { System.err.println( "Caught ArrayIndexOutOfBoundsException"); e.printStackTrace(); x--; } finally { System.out.println("Are we done yet?"); } } System.out.println("Now, we're done."); } } ======================================================================== Caught ArrayIndexOutOfBoundsException java.lang.ArrayIndexOutOfBoundsException: 5 at 第十二章异常处理.No5Ex.main(No5Ex.java:13) Caught ArrayIndexOutOfBoundsException java.lang.ArrayIndexOutOfBoundsException: 4 at 第十二章异常处理.No5Ex.main(No5Ex.java:13) Caught ArrayIndexOutOfBoundsException java.lang.ArrayIndexOutOfBoundsException: 3 at 第十二章异常处理.No5Ex.main(No5Ex.java:13) Caught ArrayIndexOutOfBoundsException java.lang.ArrayIndexOutOfBoundsException: 2 at 第十二章异常处理.No5Ex.main(No5Ex.java:13) Are we done yet? Are we done yet? Are we done yet? Are we done yet? 1 Are we done yet? Now, we're done.
12.4.1 异常与记录日志
使用 java.util.logging 工具将输出记录到日志中。
LoggingException将所有记录日志的基础设施都构建在异常自身中,使得它所使用的方式非常方便。
练习6
/* Create two exception classes, each of which performs its own logging * automtically. Demonstrate that these work. */ import java.util.logging.*; import java.io.*; class Oops1 extends Exception { private static Logger logger = Logger.getLogger("LoggingException");//与错误相关的包名或者类名字 public Oops1() { StringWriter trace = new StringWriter(); printStackTrace(new PrintWriter(trace));//获取抛出处的栈轨迹,接受Java。io。PrintWriter对象作为参数从而产生字符串 logger.severe(trace.toString());//severe 直接调用与日志级别相关联的方法 } } class Oops2 extends Exception { private static Logger logger = Logger.getLogger("LoggingException"); public Oops2() { StringWriter trace = new StringWriter(); printStackTrace(new PrintWriter(trace)); logger.severe(trace.toString()); } } public class No6Ex { static void f() throws Oops1 { throw new Oops1(); } static void g() throws Oops2 { throw new Oops2(); } public static void main(String[] args) { try { f(); } catch(Exception Oops1) {} try { g(); } catch(Exception Oops2) {} } } ======================================================================= 二月 16, 2021 9:44:22 下午 第十二章异常处理.Oops1 <init> 严重: 第十二章异常处理.Oops1 at 第十二章异常处理.No6Ex.f(No6Ex.java:29) at 第十二章异常处理.No6Ex.main(No6Ex.java:36) 二月 16, 2021 9:44:22 下午 第十二章异常处理.Oops2 <init> 严重: 第十二章异常处理.Oops2 at 第十二章异常处理.No6Ex.g(No6Ex.java:32) at 第十二章异常处理.No6Ex.main(No6Ex.java:39)
练习7
// Modify Exercise 3 so that the catch clause logs the result. import java.util.logging.*; import java.io.*; public class No7Ex { private static int[] ia = new int[2]; private static Logger logger = Logger.getLogger("Ex7 Exceptions"); static void logException(Exception e) { // Exception e argument StringWriter trace = new StringWriter(); e.printStackTrace(new PrintWriter(trace)); logger.severe(trace.toString()); } public static void main(String[] args) { try { ia[2] = 3; } catch(ArrayIndexOutOfBoundsException e) { System.err.println( "Caught ArrayIndexOutOfBoundsException"); e.printStackTrace(); // call logging method: logException(e); } } } ==================================================================== Caught ArrayIndexOutOfBoundsException java.lang.ArrayIndexOutOfBoundsException: 2 at 第十二章异常处理.No7Ex.main(No7Ex.java:18) 二月 16, 2021 10:04:45 下午 第十二章异常处理.No7Ex logException 严重: java.lang.ArrayIndexOutOfBoundsException: 2 at 第十二章异常处理.No7Ex.main(No7Ex.java:18)
12.5 异常说明
Java鼓励人们把方法可能会抛出的异常告知使用此方法的客户端程序员,它使得调用者能确切知道写什么样的代码可以捕获所有潜在的异常。
Java使用了一种语法,使你能以礼貌的方式告知客户端程序员某个方法可能会抛出异常 ,然后客户端程序员就可以进行相应的处理。这样异常说明。它属于方法声明的一部分,紧跟在形式参数列表之后。
练习8
/* Write a class with a method that throws an exception of the type created * in Exercise 4. Try compiling it without an exception specification to see * what the compiler says. Add the appropriate exception specification. Try * out your class and its exception inside a try-catch clause. */ class Test8 { public static void f() throws Exception4 { System.out.println("f()"); throw new Exception4("Ouch from f()"); } } public class No8Ex { public static void main(String[] args) { try { Test8 t = new Test8(); t.f(); } catch(Exception4 e) { System.err.println("Caught Exception4"); e.printStackTrace(); e.showS(); } } } ============================================================ f() Exception4() Message from Exception4: Ouch from f() Caught Exception4 第十二章异常处理.Exception4: Ouch from f() at 第十二章异常处理.Test8.f(No8Ex.java:13) at 第十二章异常处理.No8Ex.main(No8Ex.java:21)
12.6 捕获所有异常
捕获异常的基类Exception
放在处理程序列表的末尾,以防它抢在其他处理程序之前先把异常捕获了
Exception没有太多的具体信息,可以调用它的基类Throwable继承方法:
String getMessage()
String getLocalizeMessage()
来获取详细信息,或用本地语言表示详细信息。
String toString()
返回对Throwable的简单描述,要是有详细信息的话,也会把它包含在内:
void printStrackTree() 输出到标准错误
void printStackTree(PrintStream) 选择PrintStream为输出流
void printStackTree(java.io.PrintWriter) 选择Jav.io.PrintWriter 为输出流
打印Throwable和Throwable的调用栈轨迹”把你带到异常抛出点“的方法调用序列
练习9
/* Create three new types of exceptions. Write a class with a method that * throws all three. In main(), call the method but only use a single catch * clause that will catch all three types of exceptions. */ class ExceptionA extends Exception { ExceptionA(String msg) { super(msg); } } class ExceptionB extends Exception { ExceptionB(String msg) { super(msg); } } class ExceptionC extends Exception { ExceptionC(String msg) { super(msg); } } public class No9Ex { public static void f(int x) throws ExceptionA, ExceptionB, ExceptionC { if(x < 0) throw new ExceptionA("x < 0"); if(x == 0) throw new ExceptionB("x == 0"); if(x > 0) throw new ExceptionC("x > 0"); } public static void main(String[] args) { try { // f(0); f(1); f(-1); // will catch any Exception type: } catch(Exception e) { System.out.println("Caught Exception"); e.printStackTrace(System.out); } } } ====================================================================Caught Exception 第十二章异常处理.ExceptionC: x > 0 at 第十二章异常处理.No9Ex.f(No9Ex.java:24) at 第十二章异常处理.No9Ex.main(No9Ex.java:29)
12.6.1 栈轨迹
printStrackTrace()方法提供的信息可通过 getStackTree()方法来直接访问,这个方法返回一个由栈轨迹中的元素所构成的数组。其中每一个元素表示栈的一帧,元素0表示栈顶元素,调用序列中的最后一个方法调用,最后一个元素是调用序列中的第一个方法调用。
12.6.1 重新抛出异常
重抛异常会把异常抛给上一级环境中的异常处理程序,PrintStackTrace()方法显示的将原来异常抛出点的调用栈西悉尼,而不是重新抛出点的信息。
使用fillInStackTrace(),返回一个Throwable对象,当前调用栈信息填入原来那个异常对象而建立的,异常新发地,有关原来异常发生点地信息丢失,剩下地是与新的抛出点有关的信息
不必对异常对象地清理而担心,它们都是new在堆上创建地对象,垃圾回收期会自动把它们清理掉
12.6.3 异常链
想在捕获一个异常后抛出另一个异常,并且希望把原始地异常地信息保存下来,被称为异常链
所有的Throwable都可以接受一个cause对象作为参数,cause用来表示原始异常,这样传递给新异常。
三种基本异常类提供了带cause的参数构造器:
- Error java 虚拟机报告系统错误
- Exception
- RuntimeException
如果要把其他类型连接起来,应该使用initCause()方法
练习10
/* Create a class with two methods, f() and g(). In g(), throw an exception of * a new type that you define. In f(), call g(), catch its exception and, in the * catch clause, throw a different exception (of a second type that you define). * Test your code in main(). */ class GException extends Exception { GException(String s) { super(s); } } class HException extends Exception { HException(String s) { super(s); } } public class No10Ex { static void f() { try { try { g(); } catch(GException ge) { System.out.println("Caught GException in f inner try"); ge.printStackTrace(); throw new HException("from f(), inner try"); } } catch(HException he) { System.out.println("Caught HException in f() outer try"); he.printStackTrace(System.out); } } static void g() throws GException { throw new GException("from g()"); } public static void main(String[] args) { f(); } } ====================================================================== Caught GException in f inner try Caught HException in f() outer try 第十二章异常处理.HException: from f(), inner try at 第十二章异常处理.No10Ex.f(No10Ex.java:25) at 第十二章异常处理.No10Ex.main(No10Ex.java:36) 第十二章异常处理.GException: from g() at 第十二章异常处理.No10Ex.g(No10Ex.java:33) at 第十二章异常处理.No10Ex.f(No10Ex.java:21) at 第十二章异常处理.No10Ex.main(No10Ex.java:36)
练习11
// TIJ4 Chapter Exceptions, Exercise 11, page 468 /* Repeat the previous exercise, but inside the catch clause, wrap g()'s * exception in a RuntimeException. */ public class No11Ex { static void f() { try { g(); } catch(GException ge) { System.out.println("Caught GException in f try"); ge.printStackTrace(); throw new RuntimeException(ge); } } static void g() throws GException { throw new GException("from g()"); } public static void main(String[] args) { f(); } } ============================================================== Caught GException in f try 第十二章异常处理.GException: from g() at 第十二章异常处理.No11Ex.g(No11Ex.java:20) at 第十二章异常处理.No11Ex.f(No11Ex.java:12) at 第十二章异常处理.No11Ex.main(No11Ex.java:23) Exception in thread "main" java.lang.RuntimeException: 第十二章异常处理.GException: from g() at 第十二章异常处理.No11Ex.f(No11Ex.java:16) at 第十二章异常处理.No11Ex.main(No11Ex.java:23) Caused by: 第十二章异常处理.GException: from g() at 第十二章异常处理.No11Ex.g(No11Ex.java:20) at 第十二章异常处理.No11Ex.f(No11Ex.java:12) ... 1 more
12.7 Java标准异常
Throwable这个Java类被用来表示任何可以作为异常被抛出的类。
两种类型:
- Error用来表示编译时和系统错误
- Exception 而可以被抛出的基本类型,Java类库,用户方法以及运行时故障都可能抛出Exception型异常。
12.7.1 特例:RuntimeException
Java的标准运行时会检查传递给方法的每个引用其是否为null,所以你不必对每个传递给方法的每个引用都检查其是否为null
运行时异常类型有很多,它们会自动被Java虚拟机抛出,所以不必再异常说明中把它们列出来。这些异常都是RuntimeException继承过来的,”不受检查异常“。这种异常属于错误,将被自动捕获,就不用你亲自动手了。
只能在代码中忽略RuntimeException(及其子类)类型的异常,其他类型异常的处理都是由编译器强制实施的。究其原因,RuntimeException代表的是编程错误:
- 无法预料的错误,你从控制范围之外传递进来的null引用
- 在代码进行中检查的错误。
异常被设计
- 处理一些烦人的运行时错误,代码控制能力之外的因素导致
- 发现某些编辑器无法检测道德编程错误
12.8 使用finally进行清理
无论try块中的异常是否抛出,他都能得到执行。这通常适用于内存回收之外的情况
12.8.1 finally用来做什么
对于没有垃圾回收和 析构函数(当对象不再被使用的时候调用的函数C++)的语言来说,finally非常重要。它是程序员保证:无论try块里发生了什么,内存总能得到释放。Java什么情况下才能用的finally呢?
当要把除内存资源之外的资源恢复到它们的初始状态时,就要用到finally语句
- 已经打开的文件或者网络连接
- 在屏幕上画图形
- 外部世界的某个开关
在异常没有被当前的异常处理程序捕获的情况下,异常处理机制也会在跳到更高的一层的异常处理程序之前,执行finally语句,当涉及break和continue语句的时候,finally语句也会得到执行。
练习13
/* Modify Exercise 9 by adding a finally clause. Verify that your * finally clause is executed, even if a NullPointerException is thrown. */ class ExceptionA extends Exception { ExceptionA(String msg) { super(msg); } } class ExceptionB extends Exception { ExceptionB(String msg) { super(msg); } } class ExceptionC extends Exception { ExceptionC(String msg) { super(msg); } } public class No13Ex { // array element will be initialized to null: private static Integer[] x = new Integer[1]; public static void f(int x) throws ExceptionA, ExceptionB, ExceptionC { if(x < 0) throw new ExceptionA("x < 0"); if(x == 0) throw new ExceptionB("x == 0"); if(x > 0) throw new ExceptionC("x > 0"); } public static void main(String[] args) { try { // to throw NullPointerException: f(x[0]); f(0); f(1); f(-1); // will catch any Exception type: } catch(Exception e) { System.out.println("Caught Exception"); e.printStackTrace(System.out); } finally { System.out.println("made it to finally"); } } } ========================================================================= Caught Exception 第十二章异常处理.ExceptionB: x == 0 at 第十二章异常处理.No13Ex.f(No13Ex.java:26) at 第十二章异常处理.No13Ex.main(No13Ex.java:33) made it to finally
练习14
// Show that the OnOffSwitch.java can fail by throwing a // RuntimeException inside the try block. public class OnOffSwitch14 { private static Switch sw = new Switch(); static Integer[] x = new Integer[1]; public static void f(int i) throws OnOffException1, OnOffException2 {} public static void main(String[] args) { try { sw.on(); // Code that can throw RuntimeException // and leave Switch on: f(x[0]); sw.off(); } catch(OnOffException1 e) { System.out.println("OnOffException1"); sw.off(); } catch(OnOffException2 e) { System.out.println("OnOffException2"); sw.off(); } } }
练习15
// Show that WithFinally.java doesn't fail by throwing a // RuntimeException inside the try block. public class WithFinally15 { private static Switch sw = new Switch(); // set up x[0] = null: private static Integer[] x = new Integer[1]; public static void f(int i) throws OnOffException1, OnOffException2 {} public static void main(String[] args) { try { sw.on(); // Code to throw NullPointerException: f(x[0]); } catch(OnOffException1 e) { System.out.println("OnOffException1"); } catch(OnOffException2 e) { System.out.println("OnOffException2"); } finally { sw.off(); } } }
12.8.2 在return中使用finally
因为finally子句总归是会执行的,所以在一个方法中,可以多个点返回,并且可以保证重要的清理工作仍旧会执行 。
18.3.3 缺憾:异常丢失
Java的异常实现也有瑕疵。程序出错的标志,
用某些特殊的final子句,就会发生这种情况
- 里层没有catch里层try语句直接final
- final子句return 即使抛出了异常,不会产生任何输出
练习18
// Add a second level of exception loss to LostMessage.java so that the // HoHumException is itself replaced by a third exception. class VeryImportantException extends Exception { public String toString() { return "A very important exception!"; } } class HoHumException extends Exception { public String toString() { return "A trivial exception"; } } class MeaninglessException extends Exception { public String toString() { return "A meaningless exception"; } } public class No18Ex { void f() throws VeryImportantException { throw new VeryImportantException(); } void dispose() throws HoHumException { throw new HoHumException(); } void eliminate() throws MeaninglessException { throw new MeaninglessException(); } public static void main(String[] args) { try { No18Ex lm = new No18Ex(); try { try { lm.f(); lm.dispose(); } finally { lm.eliminate(); } } catch(Exception e) { System.out.println(e); } } catch(Exception e) { System.out.println(e); } } } ========================================================== A meaningless exception
练习19
public class LostMessageFound19 { void f() throws VeryImportantException { throw new VeryImportantException(); } void dispose() throws HoHumException { throw new HoHumException(); } public static void main(String[] args) { try { LostMessageFound19 lmf = new LostMessageFound19(); try { lmf.f(); } catch(Exception e) { System.out.println(e); } finally { lmf.dispose(); } } catch(Exception e) { System.out.println(e); } } } A very important exception! A trivial exception
12.9 异常的限制
当覆盖方法的时候,只能抛出在其基类方法的异常说明里列出的那些异常。
这意味着当基类使用的代码应用到其派生类对象的时候,一样能够工作(面向对象的基本概念):
-
方法声明将抛出异常,但实际上没有抛出,这种方式使你能强制用户去捕获可能在覆盖后的event()版本中增加的异常,所以它很合理。这对于抽象方法同样成立。
-
如果一个类继承了基类和实现了一个接口,那么接口不能向基类中现有的方法增加异常,或改变异常
-
如果接口里定义的方法不是来自基类,那么方法抛出什么样的异常也都没有问题。或者实现现有方法,但可以不抛出异常
-
对构造器没有限制,继承类的构造器可以抛出任何异常,而不必理会基类构造所抛出的异常,但是必须包含基类构造器异常
-
派生类构造器不能捕获基类构造器异常
-
通过强制派生类遵守基类方法的异常说明,对象的可替换性得到了保证
-
可以向上转型为基类,编译器就会要求你捕获基类的异常,额能产生更强制的异常类代码
继承过程中,编译器会对异常进行强制要求,但异常说明本身并不属于方法类型的一部分,不基于异常说明来重载方法。一个出现在基类方法的异常说明的异常,不一定出现在派生类方法的异常说明里。在继承和覆盖的过程中,基类的方法必须出现在派生类里,”某个特定方法的异常说明接口不是变大而是变小的,与接口和类在继承时情形相反 “
练习20
/* MOdify StormyInning.java by adding an UmpireArgument exception type * and methods that throw this exception. Test the modified hierarchy. */ // Overridden methods may throw only the exceptions // specified in their base-class versions, or exceptions // derived from the base-class exceptions. import 第十章内部类.No20; class BaseballException extends Exception {} class Foul extends BaseballException {} class Strike extends BaseballException {} class UmpireArgument extends BaseballException {} class ThrownFromGame extends UmpireArgument {} abstract class Inning { public Inning() throws BaseballException {} public void event() throws BaseballException { // Doesn't actually have to throw anything } public abstract void atBat() throws Strike, Foul, UmpireArgument; public void questionableCall() throws UmpireArgument {} public void walk() {} // Throws no checked exceptions } class StormException extends Exception {} class RainedOut extends StormException {} class PopFoul extends Foul {} interface Storm { public void event() throws RainedOut; public void rainHard() throws RainedOut; } public class No20StormyInning extends Inning implements Storm { // OK to add new exceptions for constructors, but you // must deal with the base constructor exceptions: public No20StormyInning() throws UmpireArgument, RainedOut, BaseballException {} public No20StormyInning(String s) throws Foul, BaseballException {} // Regular methods must comform to base class: //! void walk() throws PopFoul {} // Compile error // Interface CANNOT add exceptions to existing // methods from the base class: //! public void event() throws RainedOut {} // If method doesn't already exist in the // base class, the exception is OK: public void rainHard() throws RainedOut {} // You can choose to not throw any exceptions, // even if the base class version does: public void event() {} // Overridden methods can throw inherited exceptions: public void atBat() throws PopFoul, ThrownFromGame { throw new ThrownFromGame(); } public void questionableCall() throws UmpireArgument { throw new UmpireArgument(); } public static void main(String[] args) { try { No20StormyInning si = new No20StormyInning(); si.atBat(); si.questionableCall(); } catch(PopFoul e) { System.out.println("Pop foul"); } catch(UmpireArgument e) { System.out.println("Umpire argument (StormyInning20)"); // } catch(ThrownFromGame e) { // System.out.println("Thrown from game"); } catch(RainedOut e) { System.out.println("Rained out"); } catch(BaseballException e) { System.out.println("Generic baseball exception"); } // Strike not thrown in derived version. try { // What happens if you upcast? Inning i = new No20StormyInning(); i.atBat(); // You must catch the exceptions from // the base-class version of the method: } catch(Strike e) { System.out.println("Strike"); } catch(Foul e) { System.out.println("Foul"); } catch(ThrownFromGame e) { System.out.println("Thrown from game (Inning)"); } catch(RainedOut e) { System.out.println("Rained out"); } catch(BaseballException e) { System.out.println("Generic baseball exception"); } } } ======================================================================== Umpire argument (StormyInning20) Thrown from game (Inning)
12.10 构造器
如果异常发生了,所有的东西能被正确的清理吗?大多数情况时非常安全的,但涉及构造器时,问题就出现了,构造器会把对象设置成安全的初始状态。编写构造器时要格外小心。如果构造器抛出异常,就不能很好的初始化,导致清理行为也可能不能正常工作。
处理那些具有可失败的构造器,且需要清理的对象。为了正确处理这种情况,对于每个构造器,都必须包含在其自己的try-finally语句中,并且每个对象构造必须都跟随一个try-fianlly语句块以确保清理。
练习21
// Demonstrate that a derived-class constructor cannot catch exceptions thrown // by its base-class constructor. class BaseException extends Exception {} class Base { Base() throws BaseException { throw new BaseException(); } } class Derived extends Base { // BaseException must be caught (no way) or // declared to be thrown: Derived() throws BaseException { super(); // not this way, 'catch' without 'try' not allowed: // catch(BaseException e) {} // not this way either, because call to super // must be first statement in constructor: // try { // super(); // } catch(BaseException e) {} } } public class No21Derived { public static void main(String[] args) { try { Derived d = new Derived(); } catch(BaseException e) { System.out.println("BaseException caught in main()"); } } } ======================================================================== BaseException caught in main()
练习22
/* Create a class called FailingConstructor with a constructor that might fail * partway through the construction process and throw an exception. In main(), * write code that properly guards against this failure. */ public class No22Ex { Integer[] ia = new Integer[2]; String s; No22Ex(String s) throws Exception { ia[0] = 0; ia[1] = 1; ia[2] = 2; this.s = s; } public static void main(String[] args) { try { No22Ex fc = new No22Ex("hi"); } catch(Exception e) { System.err.println("Caught Exception in main()"); e.printStackTrace(System.err); } finally { } } } =================================================================== Caught Exception in main() java.lang.ArrayIndexOutOfBoundsException: 2 at 第十二章异常处理.No22Ex.<init>(No22Ex.java:13) at 第十二章异常处理.No22Ex.main(No22Ex.java:18)
练习23
/* Add a class with a dispose() method to the previous exercise. Modify * FailingConstructor so that the constructor creates one of these disposable * objects, after which the constructor might through an exception, after which * it creates a second disposable member object. Write code to properly guard * against failure, and in main() verify that all possible failure situations * are covered. */ // This solution satisfies the conditions called for in FailingConstructor23 // constructor, which catches its own exceptions. class Disposable { private static int counter = 0; private int id = counter++; private boolean disposed; Disposable() { disposed = false; } void dispose() { disposed = true; } String checkStatus() { return (id + " " + (disposed ? "disposed" : "not disposed")); } } public class No22FailingConstructor { private Integer[] ia = new Integer[2]; private static Disposable d0; private static Disposable d1; No22FailingConstructor() throws Exception { try { d0 = new Disposable(); try { ia[2] = 2; // causes exception thrown and // caught in middle try loop try { d1 = new Disposable(); } catch(Exception e) { System.out.println("Caught e in inner try loop"); e.printStackTrace(System.err); System.out.println("Failed to create d1"); } } catch(Exception e) { System.out.println("Caught e in middle try loop"); e.printStackTrace(System.err); System.out.println("Disposing d0"); d0.dispose(); // d0 would have been created } } catch(Exception e) { System.out.println("Caught e in outer try loop"); e.printStackTrace(System.err); System.out.println("Failed to create d0"); } } public static void main(String[] args) { try { // the constructor catches its own exceptions: No22FailingConstructor fc = new No22FailingConstructor(); } catch(Exception e) { System.err.println("Caught Exception in main()"); e.printStackTrace(System.err); } } } ========================================================= Caught e in middle try loop Disposing d0 java.lang.ArrayIndexOutOfBoundsException: 2 at 第十二章异常处理.No22FailingConstructor.<init>(No22FailingConstructor.java:36) at 第十二章异常处理.No22FailingConstructor.main(No22FailingConstructor.java:61)
练习24
/* Add a dipsose() method to the FailingConstructor class and write code to properly use * this class. */ // Solution modeled from examples in text: import java.io.*; public class No24Ex { private BufferedReader in; public No24Ex(String fname) throws Exception { try { in = new BufferedReader(new FileReader(fname)); } catch(FileNotFoundException e) { System.out.println("Could not find file " + fname); throw e; } catch(Exception e) { try { in.close(); } catch(IOException e2) { System.out.println("in.close() failed"); } throw e; } } public String getLine() { String s; try { s = in.readLine(); } catch(IOException e) { throw new RuntimeException("readLine() failed"); } return s; } public void dispose() { try { in.close(); System.out.println("dispose() successful"); } catch(IOException e2) { throw new RuntimeException("in.close() failed"); } } public static void main(String[] args) { try { No24Ex fc = new No24Ex("No22Ex.java"); try { String s; int i = 1; while((s = fc.getLine()) != null) { // code to print to new file: // println(i + " " + s); // i++; } } catch(Exception e) { System.out.println("Exception caught in main()"); e.printStackTrace(System.err); } finally { fc.dispose(); } } catch(Exception e) { System.out.println("FailingConstructor22b construction failed"); } } } =================================================== Could not find file No22Ex.java FailingConstructor22b construction failed
12.11 异常匹配
抛出异常的时候,异常处理系统会按照代码书写顺序找出 最近 的处理程序。找到匹配的处理程序之后,它就认为异常将得到处理,然后就不再继续查找
派生类的对象也可以匹配其基类的处理程序
不能把基类的 catch 子句放在最外面,把派生类的异常放在里面,这样派生类异常永远得不到执行
/* Create a three-level hierarchy of exceptions. Now create a * base-class A with a method that throws an exception at the base * of your hierarchy. Inherit B from A and override the method so * it throws an exception at level two of your hierarchy. Repeat by * inheriting class C from B. In main(), create a C and upcast it * to A, then call the method. */ class LevelOneException extends Exception {} class LevelTwoException extends LevelOneException {} class LevelThreeException extends LevelTwoException {} class A { void f() throws LevelOneException { throw new LevelOneException(); } } class B extends A { void f() throws LevelTwoException { throw new LevelTwoException(); } } class C extends B { void f() throws LevelThreeException { throw new LevelThreeException(); } } public class No25Ex { public static void main(String[] args) { A a = new C(); try { a.f(); } catch(LevelThreeException e3) { System.out.println("Caught e3"); } catch(LevelTwoException e2) { System.out.println("Caught e2"); } catch(LevelOneException e1) { System.out.println("Caught e1"); } } } ================================================================= Caught e3
12.12 其他可选方式
异常处理系统就像一个活门(trap door),使你能放弃程序的正常执行序列。
重要原则:只有在你知道如果处理的情况下才捕获异常
重要目标:错误处理的代码同错误发生的地点相分离
异常检查会强制你在可能还没准备好处理错误的时候被迫假设catch语句,导致吞噬则害错误(harmful is swallowed)Java是强静态语言, 编译时就做类型检查的语言。
研究 被检查的异常 及其并发症,采取什么方法来解决问题
Java是强类型语言(编译时就会做类型检查的语言),要对类型检查持怀疑态度。(所有模型都是错误的,但是都是有用的)
- 不在于编译器是否会强制程序员去处理错误,而是要有一致的,使用异常来报告错误的模型
- 不在于什么时候进行检查,而是一定要有类型检查,也就是说必须强制程序使用正确的类型,至于这种强制施加于编译时还是运行时,那到没关系
通过把异常传递给控制台,就不必在main()里写try catch子句了
把”被检查的异常“转换为”不坚持的异常“,可以把”被检查的异常“包装进RuntimeException里面。
把 被检查的异常 这种功能屏蔽
练习27
// Modify Exercise 3 to convert the exception to a Runtime Exception. public class No27Ex { private static int[] ia = new int[2]; public static void main(String[] args) { try { ia[2] = 3; } catch(ArrayIndexOutOfBoundsException e) { // convert to RuntimeException: throw new RuntimeException(e); } } }
练习28
/* Modify Exercise 4 so that the custom exception class inherits from * RuntimeException, and show that the compiler allows you to leave * out the try block. */ class Exception28 extends RuntimeException { private String msg; Exception28(String msg) { super(msg); System.out.println("Exception28()"); this.msg = msg; } protected void showS() { System.out.println("Message from Exception4: " + msg); } } public class Ex28 { public static void f() throws Exception28 { System.out.println("f()"); throw new Exception28("Ouch from f()"); } public static void main(String[] args) { f(); } } ============================================================== f() Exception28() Exception in thread "main" 第十二章异常处理.Exception28: Ouch from f() at 第十二章异常处理.No28Ex.f(No28Ex.java:23) at 第十二章异常处理.No28Ex.main(No28Ex.java:26)
练习29
/* Modify all the exception types in StormyInning.java so that they extend * RuntimeException, and show that no exception specifications or try blocks * are necessary. Remove the '//!' comments and show how the methods can be * compiled without specifications. */ class BaseballException extends RuntimeException {} class Foul extends RuntimeException {} class Strike extends RuntimeException {} abstract class Inning { public Inning() {} public void event() {} public abstract void atBat(); public void walk() {} } class StormException extends RuntimeException {} class RainedOut extends RuntimeException {} class PopFoul extends RuntimeException {} interface Storm { public void event(); public void rainHard(); } public class StormyInning29 extends Inning implements Storm { public StormyInning29() {} public StormyInning29(String s) {} public void walk() {} public void event() {} public void rainHard() {} public void atBat() {} public static void main(String[] args) { StormyInning29 si = new StormyInning29(); si.atBat(); // What happens if you upcast? Inning i = new StormyInning29(); i.atBat(); } }
练习30
/* Modify Human.java so that the exceptions inherit from * RuntimeException. Modify main() so that the technique * in TurnOffChecking.java is used to handle the different * types of exceptions. */ class Annoyance extends RuntimeException {} class Sneeze extends Annoyance {} class WrapCheckedExceptions { void throwRuntimeException(int type) { try { switch(type) { case(0): throw new Annoyance(); case(1): throw new Sneeze(); case(2): throw new RuntimeException("Where am I?"); default: return; } } catch(Exception e) { // Adapt to unchecked: throw new RuntimeException(e); } } } public class No30Ex { public static void main(String[] args) { WrapCheckedExceptions wce = new WrapCheckedExceptions(); for(int i = 0; i < 3; i++) try { if(i < 3) wce.throwRuntimeException(i); else throw new RuntimeException(); } catch(RuntimeException re) { try { throw re.getCause(); } catch(Sneeze e) { System.out.print("Sneeze: " + e); } catch(Annoyance e) { System.out.println("Annoyance: " + e); } catch(Throwable e) { System.out.println("Throwable: " + e); } } } } ===================================================================== Annoyance: 第十二章异常处理.Annoyance Sneeze: 第十二章异常处理.SneezeThrowable: java.lang.RuntimeException: Where am I?
12.13 异常使用指南
12.14 总结
如果不使用异常,那你只能完成很有限的工作。报告功能时异常的精髓所在,恢复可能不占10%
,恢复也只是展开异常栈。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!