thinking-in-java(12)通过异常处理错误

来源:互联网 发布:2010总决赛第七场数据 编辑:程序博客网 时间:2024/06/05 17:21
【12.0】开场白
1)java的基本理念:结构不佳的代码不能运行;
2)改进的错误恢复机制:是提供代码健壮性的最强有力的方式;
3)java异常:
3.1)java采用异常来提供一致的错误报告模型,使得构件能够与客户端代码可靠沟通;
3.2)java异常的目的:简化大型,可靠程序的生成,确保你的应用中没有未处理的错误;
3.3)异常处理是java中唯一正式的错误报告机制:通过编译器强制执行;

【12.1】概念
1)异常的好处:能够降低错误处理代码的复杂度;

【12.2】基本异常
1)异常情形:是指阻止当前方法或作用域继续执行的问题;
2)异常处理程序:是将程序从错误状态中恢复,以使程序要么换一种方式运行,要么继续运行下去;

【12.2.1】异常参数
1)标准异常类有2个构造器:一个是默认构造器;另一个是接受字符串作为参数的构造器;
/*异常构造器的荔枝 */public class ExceptionTest {public static void main(String[] args) {int flag = 1;if (flag == 1) {throw new NullPointerException("t = null");} else throw new NullPointerException();}}/*Exception in thread "main" java.lang.NullPointerException: t = nullat chapter12.ExceptionTest.main(ExceptionTest.java:5)*/
【代码解说】
1)把异常处理看做是一种返回机制: 因为可以用抛出异常的方式从当前作用域中退出;
2)Throwable是异常类型的基类:可以抛出任意类型的 Throwable对象;

【12.3】捕获异常
【12.3.1】try块
1)介绍:在try块中尝试各种方法调用;

【12.3.2】异常处理程序
1)catch块表示异常处理程序:在try块之后;
2)终止与恢复:
2.1)异常处理理论上有两种基本模型:终止模型 和 恢复模型;
2.2)程序员通常使用了 终止模型,而不怎么使用恢复模型;

【12.4】创建自定义异常
1)建立新异常类的最简单方法:让编译器为你产生默认构造器;这通过 继承 Exception 来实现;
/* 荔枝-自定义异常 */class SimpleException extends Exception {} // 自定义异常public class InheritingExceptions {public void f() throws SimpleException {System.out.println("Throw SimpleException from f()"); throw new SimpleException();}public static void main(String[] args) {InheritingExceptions sed = new InheritingExceptions();try {sed.f(); // 1} catch (SimpleException e) {System.out.println("Caught it!"); // 2}System.out.println("continue."); //3, 恢复模型}}/*Throw SimpleException from f()Caught it!continue.*/
2)把错误信息发送给 标准错误流 System.err 比发给 标准输出流 System.out 要好:因为System.out 也许被重定向,而System.err 不会随System.out 一起被重定向;
/* 荔枝-错误信息被定向到标准输出流 System.out 还是 标准错误输出流System.err */class MyException extends Exception {public MyException() {}public MyException(String msg) {super(msg);}}public class FullConstructors {public static void f() throws MyException {System.out.println("Throwing MyException from f()");throw new MyException();}public static void g() throws MyException {System.out.println("Throwing MyException from g()");throw new MyException("Originated in g()");}public static void main(String[] args) {try {f();} catch (MyException e) {// public final static PrintStream out = null;e.printStackTrace(System.out); // 重定向到 标准输出}try {g();} catch (MyException e) {// public final static PrintStream err = null;e.printStackTrace(System.err); // 重定向到 标准错误输出e.printStackTrace(); // (同上)重定向到 标准错误输出(默认)}}}  /*Throwing MyException from f()chapter12.MyExceptionat chapter12.FullConstructors.f(FullConstructors.java:17)at chapter12.FullConstructors.main(FullConstructors.java:27)Throwing MyException from g()chapter12.MyException: Originated in g()at chapter12.FullConstructors.g(FullConstructors.java:22)at chapter12.FullConstructors.main(FullConstructors.java:32)*/
【代码解说】
1)printStackTrace()方法:打印 “从方法调用处到抛出异常处” 的方法调用序列栈;
2)printStackTrace() 默认输出流:标准错误输出流;

【12.4.1】异常与记录日志
【荔枝-异常与记录日志 】
/* 荔枝-异常与记录日志 */class LoggingException extends Exception { // 自定义异常类型private static Logger logger = Logger.getLogger("LoggingException");public LoggingException() {StringWriter trace = new StringWriter();// step2, 6printStackTrace(new PrintWriter(trace)); // 打印异常信息,即打印方法调用序列(从方法调用处到抛异常处)到 StringWriter 输出流;// step3, 7logger.severe(trace.toString()); // 严重日志:调用与日志级别相关联的方法。(日志级别为严重 severe)}}public class LoggingExceptions {public static void main(String[] args) throws InterruptedException {try {System.out.println("抛出第一个异常"); // step1throw new LoggingException();} catch (LoggingException e) {System.err.println("Caught " + e); // step4}try {Thread.sleep(1000);System.out.println("抛出第二个异常"); // step5throw new LoggingException();} catch (LoggingException e) {System.err.println("Caught " + e); // step8}}}  /*抛出第一个异常十一月 27, 2017 11:49:38 上午 chapter12.LoggingException <init> // 日志 严重: chapter12.LoggingException // 日志 at chapter12.LoggingExceptions.main(LoggingExceptions.java:18) // 方法调用处到抛出异常处的方法调用栈Caught chapter12.LoggingException // // 异常类的toString 方法抛出第二个异常十一月 27, 2017 11:49:39 上午 chapter12.LoggingException <init> // 日志 严重: chapter12.LoggingException // 日志 at chapter12.LoggingExceptions.main(LoggingExceptions.java:25) // 方法调用处到抛出异常处的方法调用栈Caught chapter12.LoggingException // 异常类的toString 方法*/
1)通常情况:需要捕获和记录其他人编写的异常,这就必须在异常处理程序中生成日志;
/* 荔枝-在异常处理程序中生成日志 */public class LoggingExceptions2 {private static Logger logger = Logger.getLogger("LoggingExceptions2");static void logException(Exception e) {StringWriter trace = new StringWriter();logger.severe(trace.toString());}public static void main(String[] args) {try {throw new NullPointerException();} catch (NullPointerException e) {// 需要捕获和记录其他人编写的异常,这就必须在异常处理程序中生成日志;logException(e); }}} /*十一月 28, 2016 2:09:22 下午 chapter12.LoggingExceptions2 logException严重: */
2)进一步自定义异常:如加入额外的构造器和成员;
// 自定义新异常,添加了字段x 以及设定x值的 构造器和 读取数据的方法.class MyException2 extends Exception {private int x;public MyException2() {}public MyException2(String msg) {  super(msg); }public MyException2(String msg, int x) { //额外的构造器 和 成员super(msg);this.x = x;}public int val() { return x; }// 还覆盖了 Throwable.getMessage() 方法, 以产生更详细的信息.@Overridepublic String getMessage() { return "Detail Message: " + x + " " + super.getMessage();}}public class ExtraFeatures {public static void f() throws MyException2 {print("Throwing MyException2 from f()");throw new MyException2();}public static void g() throws MyException2 {print("Throwing MyException2 from g()");throw new MyException2("Originated in g()"); //额外的构造器}public static void h() throws MyException2 {print("Throwing MyException2 from h()");throw new MyException2("Originated in h()", 47); //额外的构造器}public static void main(String[] args) {try {f();} catch (MyException2 e) {e.printStackTrace(System.out);}try {g();} catch (MyException2 e) {e.printStackTrace(System.out);}try {h();} catch (MyException2 e) {e.printStackTrace(System.out);System.out.println("e.val() = " + e.val());}}}  /*Throwing MyException2 from f()chapter12.MyException2: Detail Message: 0 nullat chapter12.ExtraFeatures.f(ExtraFeatures.java:20)at chapter12.ExtraFeatures.main(ExtraFeatures.java:32)Throwing MyException2 from g()chapter12.MyException2: Detail Message: 0 Originated in g()at chapter12.ExtraFeatures.g(ExtraFeatures.java:24)at chapter12.ExtraFeatures.main(ExtraFeatures.java:37)Throwing MyException2 from h()chapter12.MyException2: Detail Message: 47 Originated in h()at chapter12.ExtraFeatures.h(ExtraFeatures.java:28)at chapter12.ExtraFeatures.main(ExtraFeatures.java:42)e.val() = 47*/
【代码解说】
1)自定义新异常,添加了字段x 以及设定x值的 构造器和 读取数据的方法;
2)覆盖了 Throwable.getMessage()方法,以产生更详细的信息;(对于异常类来说,getMessage()方法类似于 toString()方法);

【12.5】异常说明
1)异常说明:java鼓励程序员把方法可能抛出的异常告知调用该方法的程序员,这是优雅的做法;以异常说明的形式进行告知;
2)异常说明:属于方法声明,在形式参数列表之后;
3)异常说明:使用了 throws 关键字,后面跟潜在潜在异常类型列表,方法定义如下:
void f() throws Exception1, Exception2, Exception3, ... {

4)如果抛出异常而没有进行处理:编译器提醒,要么处理这个异常,要么在异常说明中表明该方法将产生异常;(这是在编译的时候作出的提醒,所以这种异常称为编译时异常)
5)【编码技巧】作弊:声明抛出异常但是实际却没有抛出。好处是,为异常先占个位置,以后抛出这种异常就不用修改已有代码。在定义抽象基类和接口时这种能力很重要的,
这样派生类或接口实现类就能够抛出这些预先声明的异常;(不能再干货)
6)被检查的异常(编译时异常):这种在编译时被强制检查的异常;

【12.6】捕获所有异常
1)通过捕获异常类型的基类 Exception 捕获所有异常;通常把 捕获Exception 的 catch 子句放在处理程序列表(catch块列表)末尾,避免它抢在其他处理程序前被捕获了;
2)public class Exception extends Throwable,Exception常用的方法列表:
getMessage():获取详细信息;getLocalizedMessage():获取本地语言表示的详细信息;toString()
3)打印Throwable 的调用栈轨迹(抛出异常处到方法调用处):
printStackTrace(); 输出到标准错误输出流;printStackTrace(PrintStream);printStackTrace(java.io.PrintWriter);
4)Throwable fillStackTrace():在Throwable对象内部记录栈帧的当前状态。这在程序重新抛出异常或错误非常有用;
5)Throwable继承自Object的其他方法:
getClass():返回对象类型的对象;getClass().getName():返回对象信息名称;getClass().getSimpleName():
【荔枝-如何使用Exception的方法】
// Exception 基类 Throwable 的方法列表.public class ExceptionMethods {public static void main(String[] args) {try {throw new Exception("My Exception");} catch (Exception e) {print("Caught Exception");print("getMessage(): " + e.getMessage()); // getMessage():My Exceptionprint("getLocalizedMessage(): " + e.getLocalizedMessage()); // getLocalizedMessage():My Exceptionprint("toString(): " + e); // toString():java.lang.Exception: My Exceptionprint("printStackTrace(): ");e.printStackTrace(System.out); // 打印抛出异常栈轨迹元素, 打印的信息通过 getStackTrace() 来直接获取;}}}  /*Caught ExceptiongetMessage():My ExceptiongetLocalizedMessage():My ExceptiontoString():java.lang.Exception: My ExceptionprintStackTrace():java.lang.Exception: My Exceptionat chapter12.ExceptionMethods.main(ExceptionMethods.java:8)*/ 
【代码解说】每个方法都比前一个方法打印出更多的信息,因为每一个都是前一个的超集;

【12.6.1】栈轨迹
1)栈轨迹:通过 printStackTrace()方法打印,打印的信息通过 getStackTrace() 来直接获取;
2)getStackTrace():该方法返回一个由栈轨迹中的元素所构成的数组 ;
// 荔枝-获取调用栈轨迹数组-getStackTrace()public class WhoCalled {static void f() {try {throw new Exception();} catch (Exception e) {// getStackTrace() 返回 栈轨迹的元素组成的数组for (StackTraceElement ste : e.getStackTrace())System.out.println(ste.getMethodName()); // 异常抛出地点的方法调用.}}static void g() {f();}static void h() {g();}public static void main(String[] args) {f();System.out.println("--------------------------------");g(); // g() -> f()System.out.println("--------------------------------");h(); // h() -> g() -> f()}} /*fmain-------------------------------- 调用栈轨迹(先进后出): f() -> g() -> main, main() 最先调用, f() 最后调用fgmain--------------------------------fghmain*/
【12.6.2】重新抛出异常:分为重新抛出同一种类型的异常 还是 抛出另外一种类型的异常
1)重新抛出异常语法:
catch(Exception e) {
throw e;
}
2)重新抛出异常:会把异常抛给上一级环境的异常处理程序, 同一个try块的后续catch子句被会略;
3)重新抛出异常-fillInStackTrace():printStackTrace() 打印的是原来异常的调用栈信息,而不是新的异常抛出处的调用栈信息;fillInStackTrace() 返回一个Throwable对象,会把当前调用栈填入原来异常对象;

【荔枝-重新抛出异常】
// fillInStackTrace()  那一行成了异常的新发生地.public class Rethrowing {public static void f() throws Exception {System.out.println("originating the exception in f()");throw new Exception("thrown from f()");}public static void g() throws Exception {try {f(); } catch (Exception e) {System.out.println("Inside g(),e.printStackTrace()");e.printStackTrace(System.out); // 打印抛出异常栈轨迹元素, 打印的信息通过 getStackTrace() 来直接获取;throw e;}}public static void h() throws Exception {try {f();} catch (Exception e) {System.out.println("Inside h(),e.printStackTrace()");e.printStackTrace(System.out);// fillInStackTrace()  那一行成了异常的新发生地.// 调用 fillInStackTrace()方法后,轨迹栈 是 main()->h(),而不是main() ->h() -> f()throw (Exception) e.fillInStackTrace(); }}public static void main(String[] args) {try {g(); // g() -> f()} catch (Exception e) {System.out.println("main: printStackTrace()");e.printStackTrace(System.out); // 打印抛出异常栈轨迹元素, 打印的信息通过 getStackTrace() 来直接获取;}System.out.println("\n\n main 方法中的 第二个 异常抛出, fillInStackTrace() 测试");try {h(); // h() -> f()} catch (Exception e) {System.out.println("main: printStackTrace()2");e.printStackTrace(System.out);}}}  /*originating the exception in f()Inside g(),e.printStackTrace()java.lang.Exception: thrown from f()at chapter12.Rethrowing.f(Rethrowing.java:6)at chapter12.Rethrowing.g(Rethrowing.java:10)at chapter12.Rethrowing.main(Rethrowing.java:28)main: printStackTrace()java.lang.Exception: thrown from f()at chapter12.Rethrowing.f(Rethrowing.java:6)at chapter12.Rethrowing.g(Rethrowing.java:10)at chapter12.Rethrowing.main(Rethrowing.java:28) main 方法中的 第二个 异常抛出originating the exception in f()Inside h(),e.printStackTrace()java.lang.Exception: thrown from f()at chapter12.Rethrowing.f(Rethrowing.java:6)at chapter12.Rethrowing.h(Rethrowing.java:19)at chapter12.Rethrowing.main(Rethrowing.java:35)main: printStackTrace()java.lang.Exception: thrown from f()at chapter12.Rethrowing.h(Rethrowing.java:23)at chapter12.Rethrowing.main(Rethrowing.java:35)*/
【荔枝-重新抛出一种新异常】
// 荔枝-重新抛出一个新异常(捕获 OneException异常后, 抛出 TwoException 异常.)class OneException extends Exception {public OneException(String s) {super(s);}}class TwoException extends Exception {public TwoException(String s) {super(s);}}public class RethrowNew {public static void f() throws OneException {System.out.println("originating the exception in f()");throw new OneException("thrown from f()");}public static void main(String[] args) {try {try {f();} catch (OneException e) {System.out.println("Caught in inner try, e.printStackTrace()");e.printStackTrace(System.out);// 在捕获异常后 抛出另外一种异常// 捕获 OneException异常后, 抛出 TwoException 异常.throw new TwoException("from inner try");}} catch (TwoException e) {System.out.println("Caught in outer try, e.printStackTrace()");e.printStackTrace(System.out); // 最外层的try-catch捕获的异常的调用栈信息 没有 f()方法调用}}}  /*originating the exception in f()Caught in inner try, e.printStackTrace()chapter12.OneException: thrown from f()at chapter12.RethrowNew.f(RethrowNew.java:16)at chapter12.RethrowNew.main(RethrowNew.java:21)Caught in outer try, e.printStackTrace()chapter12.TwoException: from inner tryat chapter12.RethrowNew.main(RethrowNew.java:25)*/
【代码解说】 最外层的catch子句打印的调用栈信息只包含 main() 方法,不包含 f()方法的调用栈信息;

【12.6.3】异常链
1)异常链:常常需要在捕获第一个异常后抛出第二个异常,但想保留第一个异常的信息, 这被称为异常链;
2)三种基本的异常构造器提供了 cause 参数, 该参数吧原始异常(第一个异常)传递给第二个异常(新异常);
分别是Error,Exception 和 RuntimeException;
3)使用 ininCause() 方法可以把其他异常链连起来,
【荔枝-使用 ininCause() 方法可以把其他异常链连起来】
// 荔枝-通过initCause() 方法把 NullPointerException 插入到 DynamicFieldsException异常链中.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];}// 其他方法都可以忽略不看,看 这个方法setField() 即可。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();// initCause() 方法把 NullPointerException 插入到 DynamicFieldsException异常链中.dfe.initCause(new NullPointerException());throw dfe; // 如果value=null,抛出异常}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 = { " + df + " }");try {df.setField("d", "A value for d");df.setField("number", 47);df.setField("number2", 48);print("df = { " + 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); // 把value设置为null,故意让setField()方法抛出异常} catch (NoSuchFieldException e) {e.printStackTrace(System.out);} catch (DynamicFieldsException e) {e.printStackTrace(System.out);}}} /*Object field = df.setField("d", null); // 仅仅打印这句代码抛出的异常信息, 该代码把value设置为null,故意让setField()方法抛出异常chapter12.DynamicFieldsExceptionat chapter12.DynamicFields.setField(DynamicFields.java:66)at chapter12.DynamicFields.main(DynamicFields.java:100)Caused by: java.lang.NullPointerException // 通过initCause() 方法把 NullPointerException 插入到 DynamicFieldsException异常链中.at chapter12.DynamicFields.setField(DynamicFields.java:68)... 1 more*/ 
【12.7】Java标准异常
1)异常基类:Throwable;有两个子类,包括 Error 和 Exception;
1.1)Error表示编译时和系统错误;(程序员不用关心)
1.2)Exception用于方法和运行时可能抛出的异常类型;(关心);
【荔枝-Throwable, Exception, RuntimeException, Error, 源码】
public class Throwable implements Serializable {public class Error extends Throwable {public class Exception extends Throwable {public class RuntimeException extends Exception {
【12.7.1】特例: RuntimeException-运行时异常
1)属于运行时异常的类型有很多:自动被jvm抛出,无需程序员抛出(但还是可以在代码中抛出 RuntimeException异常);因为运行时异常太多了,如果都去捕获的话,代码显得混乱;
2)不受检查的异常:运行时异常RuntimeException 也被称为 不受检查的异常;这种异常属于错误,被自动捕获;

【荔枝-显式抛出运行时异常】
// 荔枝-显式抛出运行时异常public class NeverCaught {static void f() {throw new RuntimeException("From f()"); // 3-抛出异常}static void g() { f(); // 2}public static void main(String[] args) {g(); // 1 }}  /*Exception in thread "main" java.lang.RuntimeException: From f()at chapter12.NeverCaught.f(NeverCaught.java:6)at chapter12.NeverCaught.g(NeverCaught.java:9)at chapter12.NeverCaught.main(NeverCaught.java:12)*/
【注意】只能在代码中忽略 RuntimeException 及其子类的异常, 其他类型异常的处理都是由编译器强制实施的。
RuntimeException代表的是编程错误;
无法预料的错误;
应该在代码中进行检查的错误;

【12.8】使用 finally 进行清理
1)finally块: 无论try块中的异常是否抛出,finally 块的内容始终执行;
【荔枝-finally块】
// finally 的经典荔枝class ThreeException extends Exception {}public class FinallyWorks {static int count = 0;public static void main(String[] args) {// 循环了两次,第一次抛出异常,第二次没有抛出异常,并在finally块中结束while (true) {try {if (count++ == 0)throw new ThreeException();System.out.println("No exception"); // 3} catch (ThreeException e) {System.out.println("ThreeException"); // 1} finally {System.out.println("In finally clause"); // 2, 4if (count == 2)break; }}}}/*ThreeExceptionIn finally clauseNo exceptionIn finally clause*/
【编程技巧】当java中的异常不允许程序回到异常抛出处,应该如何应对?把try块放在 循环里。(参考ThreeException.java 荔枝)

【12.8.1】finally 用来做什么?
1)finally块:保证 无论try块发生了什么,内存总能得到释放;
2)finally的用处:当把除内存之外的资源恢复到初始状态时,需要用到 finally块;

【荔枝- 内部finally 先于外部catch() 子句执行】
// 荔枝- 内部finally 先于外部catch() 子句执行。// 无论try块中是否抛出异常, finally语句块被执行.public class AlwaysFinally {public static void main(String[] args) {print("Entering first try block, 1"); // 1try {print("Entering second try block, 2"); // 2try {throw new FourException();// java异常不允许我们回到 异常抛出地点, // 故, 内部finally 先于外部catch() 子句执行.} finally { // 内部finallyprint("finally in 2nd try block, 3"); // 3}} catch (FourException e) { // 外部catchSystem.out.println("Caught FourException in 1st try block, 4"); // 4} finally {System.out.println("finally in 1st try block, 5"); // 5}}}  /*Entering first try block, 1Entering second try block, 2finally in 2nd try block, 3Caught FourException in 1st try block, 4finally in 1st try block, 5*/
【12.8.2】在return中使用finally:
1)return 返回前会执行finally子句或finally块中的内容;
// 荔枝-在return使用finally子句 public class MultipleReturns {public static void f(int i) {print("\n=== Initialization that requires cleanup, point = " + i);try {print("Point 1");if (i == 1)return;print("Point 2");if (i == 2)return;print("Point 3");if (i == 3)return;print("End");return;// finally 子句总是会执行.} finally {print("Performing cleanup in finally clause.");}}public static void main(String[] args) {for (int i = 1; i <= 4; i++)f(i);}} /*=== Initialization that requires cleanup, point = 1Point 1Performing cleanup in finally clause.=== Initialization that requires cleanup, point = 2Point 1Point 2Performing cleanup in finally clause.=== Initialization that requires cleanup, point = 3Point 1Point 2Point 3Performing cleanup in finally clause.=== Initialization that requires cleanup, point = 4Point 1Point 2Point 3EndPerforming cleanup in finally clause.*/

【12.8.3】缺憾:异常丢失

【荔枝】异常丢失的荔枝
// 异常丢失的荔枝class VeryImportantException extends Exception {public String toString() {return "A very important exception from VeryImportantException!";}}class HoHumException extends Exception {public String toString() {return "A trivial exception from HoHumException!";}}public class LostMessage {void throwVeryImportantException() throws VeryImportantException {throw new VeryImportantException();}void throwHoHumException() throws HoHumException {throw new HoHumException();}public static void main(String[] args) {try {LostMessage lm = new LostMessage();try {lm.throwVeryImportantException();  // 抛出  VeryImportantException 异常// 先执行内部finally子句} finally { lm.throwHoHumException(); // 抛出  HoHumException 异常}// 后执行外部 catch 子句} catch (Exception e) { System.out.println(e);}}} /*A trivial exception from HoHumException!  结果抛出了 HoHumException 异常, VeryImportantException 异常被覆盖了。*/
【代码解说】VeryImportantException 异常被 finally 子句里的 HoHumException 所取代, 抛出的VeryImportantException异常丢失了;

【荔枝】异常丢失的荔枝2
// 异常丢失荔枝2// 从finally 子句中返回,如果运行这个程序,即使抛出了异常,也不会产生任何输出;public class ExceptionSilencer {public static void main(String[] args) {try {throw new RuntimeException();} finally {return; // 从finally 子句中返回}}} /*不会产生任何输出 */ 
【编码技巧】使用finally 要防止出现 异常丢失的情况;

【12.9】异常限制
1)当子类覆盖父类方法时:只能抛出基类方法的异常说明里列出的异常;

【编码技巧】异常限制对构造器不起作用,派生类构造器不能捕获基类构造器抛出的异常;
// BaseballException异常父类class BaseballException extends Exception {} // BaseballException-棒球class Foul extends BaseballException {} // Foul-犯规class Strike extends BaseballException {} // Strike-击打 // Inning-(棒球)一局abstract class Inning { public Inning() throws BaseballException, NullPointerException {} // 构造器抛异常public void event() throws BaseballException {} // event-事件public abstract void atBat() throws Strike, Foul; // atBat-在球棒上public void walk() {} // walk-行走}class StormException extends Exception {} // StormException-暴风雨异常class RainedOut extends StormException {} // RainedOut- 因雨取消class PopFoul extends Foul {} // PopFoul-流行犯规 interface Storm {public void event() throws RainedOut;public void rainHard() throws RainedOut;}public class StormyInning extends Inning implements Storm { // StormyInning-暴风雨中的一局棒球赛 public StormyInning() throws RainedOut, BaseballException {} // 构造器抛出异常,派生类构造器抛出的异常不受父类构造器抛出异常的限制public StormyInning(String s) throws Foul, BaseballException {} // 构造器抛出异常public void rainHard() throws RainedOut {} // rainHard-下大雨public void event() {}public void atBat() throws PopFoul {}public static void main(String[] args) {try {StormyInning si = new StormyInning(); // 新建子类对象si.atBat();} catch (PopFoul e) {System.out.println("Pop foul");} catch (RainedOut e) {System.out.println("Rained out");} catch (BaseballException e) {System.out.println("Generic baseball exception");} catch(Exception e) {System.out.println("Generic Exception");}try {Inning i = new StormyInning(); // 父类指针指向子类对象i.atBat();} catch (Strike e) {System.out.println("Strike");} catch (Foul e) {System.out.println("Foul");} catch (RainedOut e) {System.out.println("Rained out");} catch (BaseballException e) {System.out.println("Generic baseball exception");}}} // 打印结果 为空
【代码解说】 Inning 是父类, StormyInning 是子类,而子类 StormyInning 抛出的异常不受父类 Inning 抛出异常的限制;如下:
public Inning() throws BaseballException, NullPointerException {} // 父类构造器抛异常;public class StormyInning extends Inning implements Storm {public StormyInning() throws RainedOut, BaseballException {} // 派生类构造器抛出的异常不受父类构造器抛出异常的限制;public StormyInning(String s) throws Foul, BaseballException {} // 派生类构造器抛出的异常不受父类构造器抛出异常的限制;// ......}
【12.10】构造器
1)问题: 如果异常发生,所有东西都能够被清理吗? 当涉及到构造器时, 问题就出现了,即便finally 也不能完全解决。
2)如果在构造器内抛出异常,清理行为就不能正常工作了。所以 编写构造器时要格外小心;
3)如果构造器(如创建文件输入流)在其执行过程中半途而废(文件路径找不到,输入流创建失败),也许该对象还没有被成功创建,而这些部分在 finally 子句中却要被清理;(这容易触发空指针异常)

【荔枝-finally块 关闭了没有打开的文件输入流,抛出空指针异常的处理方法】
// 荔枝-如何处理finally块中抛出异常的情况public class InputFile {private BufferedReader in;public InputFile(String fname) throws Exception {try {in = new BufferedReader(new FileReader(fname));} catch (FileNotFoundException e) {System.out.println("Could not open " + fname);throw e;} catch (Exception e) {try {in.close(); // close()方法也可能 抛出异常} catch (IOException e2) {System.out.println("in.close() unsuccessful");}throw e; // Rethrow} finally {// Don't close it here!!!}}public String getLine() {String s;try {s = in.readLine();} catch (IOException e) {throw new RuntimeException("readLine() failed");}return s;}// dispose-处理处置安排:关闭文件输入流public void dispose() {try {in.close();System.out.println("dispose() successful");} catch (IOException e2) {throw new RuntimeException("in.close() failed");}}} // 在构造阶段可能抛出异常, 并且要求清理的类,使用到了 嵌套try 子句public class Cleanup {static String path = System.getProperty("user.dir") + File.separator  + "src" + File.separator + "chapter12" + File.separator;// 用二层 try-catchpublic static void main1(String[] args) {try { // 外层try-catchInputFile in = null;try { // 双层 try语句块(嵌套try子句)in = new InputFile(path + "erroPath" + "Cleanup.java"); // 错误路径String s;int i = 1;while ((s = in.getLine()) != null);} catch (Exception e) {System.out.println("Caught Exception in main");e.printStackTrace(System.out);} finally {in.dispose(); // 关闭文件输入流}} catch (Exception e) {System.out.println("InputFile construction failed");}System.out.println("continue;"); // 即便抛出异常,还是继续执行;}/* 第一个main() 打印结果Could not open D:\classical_books\thinking-in-java\AThinkingInJava\src\chapter12\erroPathCleanup.javaCaught Exception in mainjava.io.FileNotFoundException: D:\classical_books\thinking-in-java\AThinkingInJava\src\chapter12\erroPathCleanup.java (系统找不到指定的文件。)at java.io.FileInputStream.open0(Native Method)at java.io.FileInputStream.open(FileInputStream.java:195)at java.io.FileInputStream.<init>(FileInputStream.java:138)at java.io.FileInputStream.<init>(FileInputStream.java:93)at java.io.FileReader.<init>(FileReader.java:58)at chapter12.InputFile.<init>(InputFile.java:11)at chapter12.Cleanup.main(Cleanup.java:14)InputFile construction failedcontinue; */// 用一层 try-catchpublic static void main(String[] args) {InputFile in = null;try { // 双层 try语句块(嵌套try子句)in = new InputFile(path + "erroPath" + "Cleanup.java"); // 错误路径String s;int i = 1;while ((s = in.getLine()) != null);} catch (Exception e) {System.out.println("Caught Exception in main");e.printStackTrace(System.out);} finally {// 关闭文件输入流(如果文件路径错误或其他错误导致文件输入流没有打开的话,in为null,会抛出空指针异常)in.dispose(); }System.out.println("continue;"); // 如果抛出异常,程序执行终止,这句无法执行;}  }/* 第二个main() 打印结果Could not open D:\classical_books\thinking-in-java\AThinkingInJava\src\chapter12\erroPathCleanup.javaCaught Exception in mainjava.io.FileNotFoundException: D:\classical_books\thinking-in-java\AThinkingInJava\src\chapter12\erroPathCleanup.java (系统找不到指定的文件。)at java.io.FileInputStream.open0(Native Method)at java.io.FileInputStream.open(FileInputStream.java:195)at java.io.FileInputStream.<init>(FileInputStream.java:138)at java.io.FileInputStream.<init>(FileInputStream.java:93)at java.io.FileReader.<init>(FileReader.java:58)at chapter12.InputFile.<init>(InputFile.java:11)at chapter12.Cleanup.main(Cleanup.java:48)Exception in thread "main" java.lang.NullPointerExceptionat chapter12.Cleanup.main(Cleanup.java:57)*/
【代码解说】
解说1)如果FileReader 构造器失败,抛出 FileNotFoundException 异常。对于这种异常,不需要关闭文件,因为这个文件还没有打开。
然而,任何其他捕获异常的catch 子句必须关闭文件,因为捕获异常时,文件已经打开了。所以 这里就矛盾了。
解说2)所以finally块中的 in.dispose() 可能抛出异常;所以还需要再封装一层 try-catch (双层try-catch);
解说3)getLine()方法将异常转换为 RuntimeException, 表示这是一个编程错误;
解说4)最安全的使用方式:使用嵌套的try子句,就如 上面的 Cleanup.java 荔枝;

【编码技巧】在创建需要清理的对象后,立即进入一个 try-finally 语句块:

【荔枝-在创建需要清理的对象后,立即进入一个 try-finally 语句块】
class NeedsCleanup {  private static long counter = 1;private final long id = counter++;public void dispose() { // 清理内存 或 关闭输入李 或 其他清理操作System.out.println("NeedsCleanup " + id + " disposed");}}class ConstructionException extends Exception {}class NeedsCleanup2 extends NeedsCleanup {public NeedsCleanup2() throws ConstructionException {} // 构造器抛出异常}// 在创建需要清理的对象后,立即进入一个 try-finally 语句块.public class CleanupIdiom {public static void main(String[] args) {// Section 1:NeedsCleanup nc1 = new NeedsCleanup();try {// ...} finally {nc1.dispose(); // // 清理内存 或 关闭输入李 或 其他清理操作}// Section 2:// If construction cannot fail you can group objects:NeedsCleanup nc2 = new NeedsCleanup();NeedsCleanup nc3 = new NeedsCleanup();// 在创建需要清理的对象后,立即进入一个 try-finally 语句块.try { // ...} finally {nc3.dispose(); // 清理内存 或 关闭输入李 或 其他清理操作nc2.dispose(); // 清理内存 或 关闭输入李 或 其他清理操作}// Section 3: 展示了如何处理那些具有可以失败的构造器,且需要清理的对象。(处理方法是 3层try-catch块 )// If construction can fail you must guard each one:try {NeedsCleanup2 nc4 = new NeedsCleanup2();try {NeedsCleanup2 nc5 = new NeedsCleanup2();// 在创建需要清理的对象后,立即进入一个 try-finally 语句块.try {// ...} finally {nc5.dispose(); // 清理内存 或 关闭输入李 或 其他清理操作}} catch (ConstructionException e) { // nc5 constructorSystem.out.println(e);} finally {nc4.dispose();}} catch (ConstructionException e) { // nc4 constructorSystem.out.println(e);}}}  /*NeedsCleanup 1 disposedNeedsCleanup 3 disposedNeedsCleanup 2 disposedNeedsCleanup 5 disposedNeedsCleanup 4 disposed*/
【代码解说】
1)Section 3 展示了如何处理那些具有可以失败的构造器,且需要清理的对象。
2)处理方法是 3层try-catch块:对于每一个构造,都必须包含在 try-finally 块中,并且每一个对象构造都必须跟随一个 try-finally 块以确保清理;

【12.11】异常匹配
1)异常处理系统找到代码书写顺序最近的异常进行处理,且不再继续查找;

【荔枝-异常匹配】
// 异常匹配的荔枝class Annoyance extends Exception {}class Sneeze extends Annoyance {}public class Human {public static void main(String[] args) {// Catch the exact type:try {throw new Sneeze(); // 抛出子类异常} catch (Sneeze s) { // 捕获子类异常System.out.println("Caught Sneeze");} catch (Annoyance a) { // 捕获基类异常System.out.println("Caught Annoyance 1");}// Catch the base type:try {throw new Sneeze(); // 抛出子类异常} catch (Annoyance a) { //// 捕获基类异常System.out.println("Caught Annoyance 2"); // 这里捕获异常}}}  /*Caught SneezeCaught Annoyance 2*/
【代码解说】 catch (Annoyance a) 会捕获Annoyance 及其子类的异常;

2)如果把捕获基类异常的catch子句放在最前面,则会把后面子类的异常捕获子句 catch 子句 屏蔽掉,如:
try{throw new Sneeze(); // 抛出子类异常} catch (Annoyance a) { // // 捕获基类异常System.out.println("Caught Annoyance 1"); // 这里捕获异常} catch (Sneeze s) { //  捕获子类异常System.out.println("Caught Sneeze"); // 异常捕获子句被屏蔽} 
3)编译器报错:编译器会发现 catch (Sneeze s) 永远不会执行,编译器报错;

【12.12】其他可选方式
1)异常处理的目标:把错误处理的代码和错误发生的地点相分离;

原创粉丝点击