十二、 通过异常处理错误
12.1 概念
把“描述在正常执行过程中做什么事”的代码和“出了问题怎么办”的代码相分离。捕获这些异常。
12.2 基本异常
12.2.1 异常参数
所有标准异常类都有两个构造器:一是默认构造器;另一个接受字符串为参数,以便能把相关信息放入异常对象的构造器。throw new NullPointerException(“t = null”);//抛出一个异常
Throwable它是异常类型的根类。通常对于不同类型的错误,要抛出相应的异常。错误信息可保存在异常对象内部或者用异常类的名称来暗示。上一层环境通过这些信息来决定如何处理异常。
12.3 捕获异常
监控区域—它是一段可能产生异常的代码,并且后面跟着处理这些异常的代码。
12.3.1 try块
如果在方法内部抛出了异常(或者在方法内部调用的其他方法抛出了异常),这个方法将在抛出异常的过程中结束。要是不希望方法就此结束,可在方法设置一个特殊的块来捕获异常。因为在这个块里“尝试”各种(可能产生异常的)方法调用,称为try块。
可把所有动作都放在try块里,只需在一个地方就可捕获所有异常。
12.3.2 异常处理程序
抛出的异常必须在某处得到处理,这个地点就是异常处理程序,而且针对每个要捕获的异常,得准备相应的处理程序。异常处理程序紧跟在try块之后,以关键字catch表示:
try{
}catch(Type1 id1){
//处理异常类型Type1,id1为标识符有时可能用不到
}
终止与恢复
终止模型:程序无法返回到异常发生的地方执行。一旦异常被抛出,就表明错误无法挽回也不能回来继续执行。
恢复模型:异常被处理之后能继续执行程序。
12.4 创建自定义异常
要自己定义异常类,必须从已有的异常类继承。
public class SimpleException extends Exception {
SimpleException(){}
//构造器带参数的输出时候就相当于对
SimpleException(String msg){
super(msg);
}
}
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();
}catch (SimpleException e){
System.err.println("Caught it!");
e.printStackTrace(System.err);
}
}
}
//output
Throw SimpleException from f()
Caught it!
com.agree.ShiEr.SimpleException: 说明
at com.agree.ShiEr.InheritingExceptions.f(InheritingExceptions.java:6)
at com.agree.ShiEr.InheritingExceptions.main(InheritingExceptions.java:12)
12.4.1 异常与记录日志
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.logging.Logger;
class LoggingException extends Exception {
private static Logger logger =
Logger.getLogger("LoggingException");
public LoggingException() {
StringWriter trace = new StringWriter();
printStackTrace(new PrintWriter(trace));
logger.severe(trace.toString());
}
}
public class LoggingExceptions {
public static void main(String[] args) {
try {
throw new LoggingException();
} catch(LoggingException e) {
System.err.println("Caught " + e);
}
try {
throw new LoggingException();
} catch(LoggingException e) {
System.err.println("Caught " + e);
}
}
} /* Output: (85% match)
六月 29, 2020 5:10:51 下午 com.agree.ShiEr.LoggingException <init>
严重: com.agree.ShiEr.LoggingException
at com.agree.ShiEr.LoggingExceptions.main(LoggingExceptions.java:6)
Caught com.agree.ShiEr.LoggingException
六月 29, 2020 5:10:51 下午 com.agree.ShiEr.LoggingException <init>
严重: com.agree.ShiEr.LoggingException
at com.agree.ShiEr.LoggingExceptions.main(LoggingExceptions.java:11)
Caught com.agree.ShiEr.LoggingException
*///:~
常见的情形是我们需要捕获和记录其他人编写的异常
public class LoggingExceptions2 {
private static Logger logger =
Logger.getLogger("LoggingExceptions2");
static void logException(Exception e) {
StringWriter trace = new StringWriter();
e.printStackTrace(new PrintWriter(trace));
logger.severe(trace.toString());
}
public static void main(String[] args) {
try {
throw new NullPointerException();
} catch(NullPointerException e) {
logException(e);
}
}
}
进一步自定义异常,加入额外的构造器和成员
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; }
//getMessage()覆盖了Throwable的方法类似于toString()方法
public String getMessage() {
return "Detail Message: "+ x + " "+ super.getMessage();
}
}
public class ExtraFeatures {
public static void f() throws MyException2 {
System.err.println("Throwing MyException2 from f()");
throw new MyException2();
}
public static void g() throws MyException2 {
System.err.println("Throwing MyException2 from g()");
throw new MyException2("Originated in g()");
}
public static void h() throws MyException2 {
System.err.println("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.err);
}
try {
g();
} catch(MyException2 e) {
e.printStackTrace(System.err);
}
try {
h();
} catch(MyException2 e) {
e.printStackTrace(System.err);
System.err.println("e.val() = " + e.val());
}
}
} /* Output:
Throwing MyException2 from f()
com.agree.ShiEr.MyException2: Detail Message: 0 null
at com.agree.ShiEr.ExtraFeatures.f(ExtraFeatures.java:6)
at com.agree.ShiEr.ExtraFeatures.main(ExtraFeatures.java:18)
Throwing MyException2 from g()
com.agree.ShiEr.MyException2: Detail Message: 0 Originated in g()
at com.agree.ShiEr.ExtraFeatures.g(ExtraFeatures.java:10)
at com.agree.ShiEr.ExtraFeatures.main(ExtraFeatures.java:23)
Throwing MyException2 from h()
com.agree.ShiEr.MyException2: Detail Message: 47 Originated in h()
at com.agree.ShiEr.ExtraFeatures.h(ExtraFeatures.java:14)
at com.agree.ShiEr.ExtraFeatures.main(ExtraFeatures.java:28)
e.val() = 47
*///:~
12.5 异常说明
以礼貌方式告知客户端程序员某个方法可能会抛出的异常类型,客户端程序员就可以进行相应的处理。.
方法名后throw 异常
public void f() throws SimpleException{
System.out.println("Throw SimpleException from f()");
throw new SimpleException("说明");
}
//输出时会显示在异常类后面进行说明
12.6 捕获所有异常
Exception是与编程有关的所有异常类的基类,它不包含具体信息,但可调用基类Throwable继承方法:
String getMessage()
String getLocalizedMessage()
toString()//获取详细信息
//调用栈轨迹
void printStackTrace()
void printStackTrace(System.err)
void printStackTrace(java.io.PrintWriter)
public class ExceptionMethods {
public static void main(String[] args) {
try {
throw new Exception("My Exception");
} catch(Exception e) {
System.err.println("Caught Exception");
System.err.println("getMessage():" + e.getMessage());
System.err.println("getLocalizedMessage():" +
e.getLocalizedMessage());
System.err.println("toString():" + e);
System.err.println("printStackTrace():");
e.printStackTrace(System.err);
}
}
}
12.6.1 栈轨迹
printStackTrace()方法所提供的信息可通过getStackTrace()方法来直接访问,这个方法将返回一个由栈轨迹中的元素所构成的数组,其中每一个元素都表示栈中的一帧。
12.6.2 重新抛出异常
有时希望把刚捕获的异常重新抛出,尤其是在使用Exception捕获所有异常的时候,既然得到了对当前对象的引用可直接抛出。
catch(Exception e){
System.out.println("An exception was thrown");
throw e;
}
重抛异常会把异常抛给上一级环境中的异常处理程序,同一try块的后续catch字句将被忽略。异常对象的所有信息都得以保持,所以高一级环境中捕获此异常的处理程序可从异常对象中得到所有信息。
printStackTrace()方法显示的将是原来异常抛出点的调用栈信息,而并非抛出点的信息。要想更新这个信息,可调用fillStackTrace()方法,将返回一个Throwable对象,它通过把当前调用栈信息填入原来那个异常对象而建立的。
public class Rethrowing {
public static void f() throws Exception{
System.err.println("originating the exception in f()");
throw new Exception("throw from f()");
}
public static void g() throws Exception{
try{
f();
}catch (Exception e){
System.err.println("Inside g() ,e.printStackTrace()");
e.printStackTrace(System.err);
throw e;
}
}
public static void h() throws Exception{
try{
f();
}catch (Exception e){
System.err.println("Inside h(),e.printStackTrace()");
e.printStackTrace();
throw (Exception)e.fillInStackTrace();
}
}
public static void main(String[] args) {
try{
g();
}catch (Exception e){
System.err.println("main:printStackTrace");
e.printStackTrace(System.err);
}
try{
h();
}catch (Exception e){
System.err.println("main:printStackTrace");
e.printStackTrace(System.err);
}
}
}
/*
originating the exception in f()
Inside g() ,e.printStackTrace()
java.lang.Exception: throw from f()
at com.agree.ShiEr.Rethrowing.f(Rethrowing.java:6)
at com.agree.ShiEr.Rethrowing.g(Rethrowing.java:10)
at com.agree.ShiEr.Rethrowing.main(Rethrowing.java:28)
main:printStackTrace
java.lang.Exception: throw from f()
at com.agree.ShiEr.Rethrowing.f(Rethrowing.java:6)
at com.agree.ShiEr.Rethrowing.g(Rethrowing.java:10)
at com.agree.ShiEr.Rethrowing.main(Rethrowing.java:28)
originating the exception in f()
Inside h(),e.printStackTrace()
java.lang.Exception: throw from f()
at com.agree.ShiEr.Rethrowing.f(Rethrowing.java:6)
at com.agree.ShiEr.Rethrowing.h(Rethrowing.java:19)
at com.agree.ShiEr.Rethrowing.main(Rethrowing.java:34)
main:printStackTrace
java.lang.Exception: throw from f()
at com.agree.ShiEr.Rethrowing.h(Rethrowing.java:23)
at com.agree.ShiEr.Rethrowing.main(Rethrowing.java:34)
*///:~
12.6.3 异常链
常常想捕获一个异常后抛出另一个异常,并且希望把原始异常的信息保存下来,这被称为异常链。
现在所有Throwable的子类在构造器中都可接受一个cause对象作为参数。这个cause就用来表示原始异常,这样通过把原始异常传递给新的异常,使得即使在当前位置创建并抛出新的异常,也能通过这异常链追踪到异常最初发生的位置。
在Throwable的子类中,只有三种基本的异常类提供了带cause参数的构造器分别是Error,Exception,以及RuntimeException。如果要把其他类型的异常链接起来,应该使用initCause()方法而不是构造器。
class DynamicFieldsException extends Exception{}
DynamicFieldsException dy = new DynamicFieldsException();
dy.initCause(new NullPointerException());
12.7 java标准异常
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q0Gv6sqm-1603261846226)(C:\Users\srqnk\AppData\Roaming\Typora\typora-user-images\1593480655843.png)]
Error(错误):编译时和系统错误
Exception(异常):是可被抛出的基本类型,是程序本身可以处理的异常
异常并非全在java.lang包里定义;有些异常是用来支持其他像util、net和io这样的程序包。例如所有的输入/输出异常都是从java.io.IOException继承而来。
12.7.1 特例:RuntimeException
不受检查的异常,不需要在异常说明方法抛出RuntimeException。其没有被捕获而直达main(),程序退出前将调用异常的printStackTrace()方法。
只能在代码忽略RuntimeException(及其子类)类型的异常,其他类型异常的处理都是编译器强制实施的。究其原因,RuntimeException代表的是编程错误。
1.无法预料的错误。比如从你控制范围之外传递进来的引用
2.应在代码中进行检查的错误(例如数组下标越界异常,注意数组的大小)。在一个地方发生异常,常常会在另一个地方导致错误。
12.8 使用finally进行清理
内存回收之外的情况,finally{}无论是否异常都会被执行
12.8.1 finally用来做什么
(析构函数:当对象不再使用时候就会被调用的函数)
除内存之外的资源恢复到它们的初始状态时,就要用到finally字句。这种需要清理的资源包括:已经打开的文件或网络连接,屏幕上画的图形,甚至可是外部世界的某个开关。
异常没有被当前的异常处理程序捕获的情况下,异常处理机制也会跳到更高一层的异常处理程序之前执行finally字句。
当涉及break和continue语句的时候,finally字句也会得到执行。
12.8.2 在return中使用finally
try{
print("Point 1");
if(i==1) return;
print("Point 2");
if(i==2) return;
}finally{
print("Performing cleanup");
}
//output
Point 1
Performing cleanup
Point 1
Point 2
Performing cleanup
12.8.3 缺憾:异常丢失
1.在finally中调用抛异常的方法会让try中的异常丢失
2.在finally字句中直接返回return,即使抛出异常也不会被输出
12.9 异常的限制
当覆盖方法的时候,只能抛出在基类方法的异常说明里列出的那些异常。
覆盖后的方法派生类可不抛出任何异常。
基类方法没有抛出异常子类是无法给他抛异常的。
12.10 构造器
异常发生时构造器会把对象设置成安全的初始状态,但打开一个文件时,只有在对象使用完毕后并且用户调用了特殊的清理方法后才能得以清理。若在构造器内部抛出异常,这些清理行为也许就不能正常工作了。
12.11 异常匹配
抛出异常的时候,异常处理系统会按照代码的书写顺序抛出“最近”的处理程序。找到匹配的处理程序之后,它就认为异常将得到处理,然后就不再继续查找。
如果捕获的是基类catch(Annoyance e)会捕获Annoyance以及所有它派生的异常。
public class Annyance extends Exception {
}
public class Sneeze extends Annyance {
Sneeze(){
System.out.println("Sneeze");
}
}
public class Human {
public static void main(String[] args) {
/* try{
throw new Sneeze();
}catch (Sneeze s){
System.out.println("Caught Sneeze");
}catch (Annyance a){
System.out.println("Caught Annyance");
}*/
try{
throw new Sneeze();
}catch (Annyance a){
System.out.println("Caught Annyance2");
}
}
}//output
//Sneeze
//Caught Sneeze
Sneeze
Caught Annyance2
12.12 其他可选方式
对于一些不知道怎么处理的被检查异常,作者推荐两种办法:
(1)在main()函数中抛出这些异常(main(String[] args) throws Exception);
(2)当在一个方法里调用别的方法。利用异常链,把被检查异常包装成RuntimeException抛出
即: throw new RuntimeException(e);
新特性:
A、允许通过相同的catch子句捕获多个异常,使用操作符 | 分隔每个异常。每个多重捕获参数都被隐式的声明为final,因此不能赋予它新的值。正常的捕获并没有final的限制,我想是因为多重捕获形式如下:
catch(IOException | ArrayIndexOutOfBoundsException e ) {
}
如果要在catch语句块中重新赋值,编译器搞不清楚e到底是第一个类型还是第二个类型。
B、重新抛出精确的异常
即:
try{
throw new Exception()
}catch(Exception e){
throw new MoreException()
}
12.13 异常使用指南
1.在恰当的级别处理问题(在知道该如何处理的情况下才捕获异常)。
2.解决问题并且重新调用产生异常的方法。
3.进行少许修补,然后绕过异常发生的地方继续执行。
4.用别的数据进行计算,以代替方法预计会返回的值。
5.把当前运行环境下能做的事情尽量做完,然后把相同的异常抛到更高层。
6.把当前运行环境下能做的事情尽量做完,然后把不同的异常抛到更高层。
7.终止程序。
8.进行简化。(如果你的异常模式使问题变得太复杂,那用起来会非常痛苦也很烦人)
9.让类库和程序更安全。(这既是在为调试做短期投资,也是在为程序的健壮性做长期投资)
rrayIndexOutOfBoundsException e ) {
}
如果要在catch语句块中重新赋值,编译器搞不清楚e到底是第一个类型还是第二个类型。
B、重新抛出精确的异常
即:
try{
throw new Exception()
}catch(Exception e){
throw new MoreException()
}
12.13 异常使用指南
1.在恰当的级别处理问题(在知道该如何处理的情况下才捕获异常)。
2.解决问题并且重新调用产生异常的方法。
3.进行少许修补,然后绕过异常发生的地方继续执行。
4.用别的数据进行计算,以代替方法预计会返回的值。
5.把当前运行环境下能做的事情尽量做完,然后把相同的异常抛到更高层。
6.把当前运行环境下能做的事情尽量做完,然后把不同的异常抛到更高层。
7.终止程序。
8.进行简化。(如果你的异常模式使问题变得太复杂,那用起来会非常痛苦也很烦人)
9.让类库和程序更安全。(这既是在为调试做短期投资,也是在为程序的健壮性做长期投资)