thinking-in-java(12)通过异常处理错误
2017-12-01 23:37
447 查看
【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个构造器:一个是默认构造器;另一个是接受字符串作为参数的构造器;
【代码解说】
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 来实现;
2)把错误信息发送给 标准错误流 System.err 比发给 标准输出流 System.out 要好:因为System.out 也许被重定向,而System.err 不会随System.out 一起被重定向;
【代码解说】
1)printStackTrace()方法:打印 “从方法调用处到抛出异常处” 的方法调用序列栈;
2)printStackTrace() 默认输出流:标准错误输出流;
【12.4.1】异常与记录日志
【荔枝-异常与记录日志 】
1)通常情况:需要捕获和记录其他人编写的异常,这就必须在异常处理程序中生成日志;
2)进一步自定义异常:如加入额外的构造器和成员;
【代码解说】
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常用的方法列表:
3)打印Throwable 的调用栈轨迹(抛出异常处到方法调用处):
4)Throwable fillStackTrace():在Throwable对象内部记录栈帧的当前状态。这在程序重新抛出异常或错误非常有用;
5)Throwable继承自Object的其他方法:
【荔枝-如何使用Exception的方法】
【代码解说】每个方法都比前一个方法打印出更多的信息,因为每一个都是前一个的超集;
【12.6.1】栈轨迹
1)栈轨迹:通过 printStackTrace()方法打印,打印的信息通过 getStackTrace() 来直接获取;
2)getStackTrace():该方法返回一个由栈轨迹中的元素所构成的数组 ;
【12.6.2】重新抛出异常:分为重新抛出同一种类型的异常 还是 抛出另外一种类型的异常
1)重新抛出异常语法:
catch(Exception e) {
throw e;
}
2)重新抛出异常:会把异常抛给上一级环境的异常处理程序, 同一个try块的后续catch子句被会略;
3)重新抛出异常-fillInStackTrace():printStackTrace() 打印的是原来异常的调用栈信息,而不是新的异常抛出处的调用栈信息;fillInStackTrace() 返回一个Throwable对象,会把当前调用栈填入原来异常对象;
【荔枝-重新抛出异常】
【荔枝-重新抛出一种新异常】
【代码解说】 最外层的catch子句打印的调用栈信息只包含 main() 方法,不包含 f()方法的调用栈信息;
【12.6.3】异常链
1)异常链:常常需要在捕获第一个异常后抛出第二个异常,但想保留第一个异常的信息, 这被称为异常链;
2)三种基本的异常构造器提供了 cause 参数, 该参数吧原始异常(第一个异常)传递给第二个异常(新异常);
分别是Error,Exception 和 RuntimeException;
3)使用 ininCause() 方法可以把其他异常链连起来,
【荔枝-使用 ininCause() 方法可以把其他异常链连起来】
【12.7】Java标准异常
1)异常基类:Throwable;有两个子类,包括 Error 和 Exception;
1.1)Error:表示编译时和系统错误;(程序员不用关心)
1.2)Exception:用于方法和运行时可能抛出的异常类型;(关心);
【荔枝-Throwable, Exception, RuntimeException, Error, 源码】
【12.7.1】特例: RuntimeException-运行时异常
1)属于运行时异常的类型有很多:自动被jvm抛出,无需程序员抛出(但还是可以在代码中抛出 RuntimeException异常);因为运行时异常太多了,如果都去捕获的话,代码显得混乱;
2)不受检查的异常:运行时异常RuntimeException 也被称为 不受检查的异常;这种异常属于错误,被自动捕获;
【荔枝-显式抛出运行时异常】
【注意】只能在代码中忽略 RuntimeException 及其子类的异常, 其他类型异常的处理都是由编译器强制实施的。
RuntimeException代表的是编程错误;
无法预料的错误;
应该在代码中进行检查的错误;
【12.8】使用 finally 进行清理
1)finally块: 无论try块中的异常是否抛出,finally 块的内容始终执行;
【荔枝-finally块】
【编程技巧】当java中的异常不允许程序回到异常抛出处,应该如何应对?把try块放在 循环里。(参考ThreeException.java 荔枝)
【12.8.1】finally 用来做什么?
1)finally块:保证 无论try块发生了什么,内存总能得到释放;
2)finally的用处:当把除内存之外的资源恢复到初始状态时,需要用到 finally块;
【荔枝- 内部finally 先于外部catch() 子句执行】
【12.8.2】在return中使用finally:
1)return 返回前会执行finally子句或finally块中的内容;
【12.8.3】缺憾:异常丢失
【荔枝】异常丢失的荔枝
【代码解说】VeryImportantException 异常被 finally 子句里的 HoHumException 所取代, 抛出的VeryImportantException异常丢失了;
【荔枝】异常丢失的荔枝2
【编码技巧】使用finally 要防止出现 异常丢失的情况;
【12.9】异常限制
1)当子类覆盖父类方法时:只能抛出基类方法的异常说明里列出的异常;
【编码技巧】异常限制对构造器不起作用,派生类构造器不能捕获基类构造器抛出的异常;
【代码解说】 Inning 是父类, StormyInning 是子类,而子类 StormyInning 抛出的异常不受父类 Inning 抛出异常的限制;如下:
【12.10】构造器
1)问题: 如果异常发生,所有东西都能够被清理吗? 当涉及到构造器时, 问题就出现了,即便finally 也不能完全解决。
2)如果在构造器内抛出异常,清理行为就不能正常工作了。所以 编写构造器时要格外小心;
3)如果构造器(如创建文件输入流)在其执行过程中半途而废(文件路径找不到,输入流创建失败),也许该对象还没有被成功创建,而这些部分在 finally 子句中却要被清理;(这容易触发空指针异常)
【荔枝-finally块 关闭了没有打开的文件输入流,抛出空指针异常的处理方法】
【代码解说】
解说1)如果FileReader 构造器失败,抛出 FileNotFoundException 异常。对于这种异常,不需要关闭文件,因为这个文件还没有打开。
然而,任何其他捕获异常的catch 子句必须关闭文件,因为捕获异常时,文件已经打开了。所以 这里就矛盾了。
解说2)所以finally块中的 in.dispose() 可能抛出异常;所以还需要再封装一层 try-catch (双层try-catch);
解说3)getLine()方法将异常转换为 RuntimeException, 表示这是一个编程错误;
解说4)最安全的使用方式:使用嵌套的try子句,就如 上面的 Cleanup.java 荔枝;
【编码技巧】在创建需要清理的对象后,立即进入一个 try-finally 语句块:
【荔枝-在创建需要清理的对象后,立即进入一个 try-finally 语句块】
【代码解说】
1)Section 3 展示了如何处理那些具有可以失败的构造器,且需要清理的对象。
2)处理方法是 3层try-catch块:对于每一个构造,都必须包含在 try-finally 块中,并且每一个对象构造都必须跟随一个 try-finally 块以确保清理;
【12.11】异常匹配
1)异常处理系统找到代码书写顺序最近的异常进行处理,且不再继续查找;
【荔枝-异常匹配】
【代码解说】 catch (Annoyance a) 会捕获Annoyance 及其子类的异常;
2)如果把捕获基类异常的catch子句放在最前面,则会把后面子类的异常捕获子句 catch 子句 屏蔽掉,如:
3)编译器报错:编译器会发现 catch (Sneeze s) 永远不会执行,编译器报错;
【12.12】其他可选方式
1)异常处理的目标:把错误处理的代码和错误发生的地点相分离;
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 = null at 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.MyException at 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, 6 printStackTrace(new PrintWriter(trace)); // 打印异常信息,即打印方法调用序列(从方法调用处到抛异常处)到 StringWriter 输出流; // step3, 7 logger.severe(trace.toString()); // 严重日志:调用与日志级别相关联的方法。(日志级别为严重 severe) } } public class LoggingExceptions { public static void main(String[] args) throws InterruptedException { try { System.out.println("抛出第一个异常"); // step1 throw new LoggingException(); } catch (LoggingException e) { System.err.println("Caught " + e); // step4 } try { Thread.sleep(1000); System.out.println("抛出第二个异常"); // step5 throw 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() 方法, 以产生更详细的信息. @Override public 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 null at 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 Exception print("getLocalizedMessage(): " + e.getLocalizedMessage()); // getLocalizedMessage():My Exception print("toString(): " + e); // toString():java.lang.Exception: My Exception print("printStackTrace(): "); e.printStackTrace(System.out); // 打印抛出异常栈轨迹元素, 打印的信息通过 getStackTrace() 来直接获取; } } } /* Caught Exception getMessage():My Exception getLocalizedMessage():My Exception toString():java.lang.Exception: My Exception printStackTrace(): java.lang.Exception: My Exception at 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() } } /* f main -------------------------------- 调用栈轨迹(先进后出): f() -> g() -> main, main() 最先调用, f() 最后调用 f g main -------------------------------- f g h main */
【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 try at 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.DynamicFieldsException at 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, 4 if (count == 2) break; } } } } /* ThreeException In finally clause No exception In 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"); // 1 try { print("Entering second try block, 2"); // 2 try { throw new FourException(); // java异常不允许我们回到 异常抛出地点, // 故, 内部finally 先于外部catch() 子句执行. } finally { // 内部finally print("finally in 2nd try block, 3"); // 3 } } catch (FourException e) { // 外部catch System.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, 1 Entering second try block, 2 finally in 2nd try block, 3 Caught FourException in 1st try block, 4 finally 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 = 1 Point 1 Performing cleanup in finally clause. === Initialization that requires cleanup, point = 2 Point 1 Point 2 Performing cleanup in finally clause. === Initialization that requires cleanup, point = 3 Point 1 Point 2 Point 3 Performing cleanup in finally clause. === Initialization that requires cleanup, point = 4 Point 1 Point 2 Point 3 End Performing 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-catch public static void main1(String[] args) { try { // 外层try-catch 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.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.java Caught Exception in main java.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 failed continue; */ // 用一层 try-catch public 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.java Caught Exception in main java.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.NullPointerException at 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 constructor System.out.println(e); } finally { nc4.dispose(); } } catch (ConstructionException e) { // nc4 constructor System.out.println(e); } } } /* NeedsCleanup 1 disposed NeedsCleanup 3 disposed NeedsCleanup 2 disposed NeedsCleanup 5 disposed NeedsCleanup 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 Sneeze Caught 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)异常处理的目标:把错误处理的代码和错误发生的地点相分离;
相关文章推荐
- Java 编程思想12通过异常处理错误12.1-概念
- java编程思想-12通过异常处理错误
- [think in java]第12章 通过异常处理错误
- day12:《Thinking in Java》笔记第十二章---通过异常处理错误
- 再读thinking in java -- 第十二章 通过异常处理错误
- Thinking in Java 第12章 通过异常处理错误
- [think in java]第12章 通过异常处理错误
- 12 我读Thinking in java 通过异常处理错误
- 通过类字面常量解释接口常量为什么只能定义为static final,类加载过程---Thinking in java
- 《java 编程思想系列之8》 通过异常处理错误
- 在VS13上编译通过的代码放在12上编译-错误:l __dtoui3 referenced in function _event_debug_map_HT_GROW
- Java通过异常处理错误
- Thinking In Java 4th Edition 关于javac的一点错误
- thinking in java test5.5练习(10)(11)(12)finalize()方法
- thinking in java test练习(12)(13)javadoc的生成
- 《Thinking in Java》十七章_容器深入研究_练习12(Page484)
- thinking in java test5.5练习(10)(11)(12)finalize()方法
- Thinking in java-12
- 《Thinking in Java》十七章_容器深入研究_练习12(Page484)
- Thinking in java 393页的错误。