第8章 异常处理
Java的异常机制主要依赖于try、catch、finally、throw和throws五个关键字
Java将异常分为两种,Checked异常和Runtime异常,Java认为Checked异常都是可以在编译阶段被处理的异常,所以它强制程序处理所有的Checked异常;而Runtime异常则无须处理。
8.2 异常处理机制
8.2.1 使用try…catch捕获异常
异常处理机制的语法格式:
try{
//业务代码
}
catch(Exception e)
{
alert(不合法)
}
try部分的代码如果出错,系统将会创建一个exception对象,称为异常抛出,
异常被抛出后,系统会拿着异常对比下面的catch
如果有catch能处理这个异常,程序继续运行,这个过程称为异常捕获
如没有catch能处理,则程序停止报错
所以的出结论,程序中的异常必须被捕获
8.2.2 异常类的继承体系
当try块中的代码抛出异常之后,将会产生一个异常对象ex,这个对象保存着详细的异常信息,当系统与catch块对比通过后,会将ex传递给catch块的形参
try执行一次,抛出一个异常对象,那么多个catch块中,将会只有一个执行
try块和catch块的花括号,不能省略
try块中的变量是局部变量,catch块中不能访问
java将所有的异常分为两类,异常(exception)和错误(Error)
它俩都继承Throwable类
Error错误,一般是与虚拟机相关德问题,这种错误无法恢复或不可能捕获,将导致程序中断
一些常见的异常
IndexOutOfBoundsException 数组越界
NumberFormatException 数字格式异常
ArithmeticException 算术异常
Exception 未知异常
NullPointerException 空指针异常
处理异常的原则:
进行异常捕获时,一定要记住先捕获小异常,再捕获大异常。
8.2.3 Java 7提供的多异常捕获
java7提供了多异常捕获,一个catch块可捕获多个异常,每个异常之间用 | 分隔
如果使用多异常捕获,需要注意,,捕获多种异常的形参,含有隐式的final修饰,因此不能在catch块中对,异常变量修改
8.2.4 访问异常信息
catch块的异常对象包含一些信息,可以通过如下方法获取信息
getMessage() 返回该异常的详细描述
printStackTrace() 将该异常的跟踪栈信息输出到,标准错误输出
printStackTrace(printStream s) 将该异常的追踪栈信息,输出到指定输出流
getStackTrace() 返回该异常的追踪栈信息
8.2.5 使用finally回收资源
Java的垃圾回收机制不会回收任何物理资源,垃圾回收机制只能回收堆内存中对象所占用的内存。
finally块,可以用于资源回收,finally块一定会被执行,
那怕 try 没有引发异常,catch 也没有被执行,甚至就算执行 return语句,finally块还是
会被执行(在return之前被执行)
只有一种方式,finally块不会被执行,就是在try或catch块中,退出了虚拟机(System.exit(1))
异常处理语法中,只有try是必须的,catch和finally都是可选的,如果只有try,那么就是将异常抛出,在类名后面使用(throws),将异常抛给主调函数
如果在类名后使用throw,则是异常抛出到本类,在本方法下的catch块中进行处理
值得注意的是,finally的代码块,也可能出现异常,所以可能出现,异常嵌套异常的情况
另外,如果在try内,和finally内,都定义了return,那么将会执行finally内的返回语句
程序在遇到return,或者throw时,会使程序结束执行,具体流程为
在try或catch中,遇到return或者throw,去寻找finally, 这里会有两种情况
一.finally正常执行,完毕之后跳回try/catch块,的return或throw
二.finally块中有return,或throw等,导致方法终止的语句,则不会返回try/catch
尽量不要在finally块中,写会使方法终止的语句,因为这样会出现不可控事件
8.2.6 异常处理的嵌套
异常处理流程代码,可以放在任何能执行代码的地方,
如:
try块,catch块,finally块
java对异常处理嵌套的,深度,没有明确限制
8.2.7 Java 7的自动关闭资源的try语句
java7增强了try语句,它允许在try关键字后,紧跟一个圆括号,该括号内可以声明一个或多个资源,
try语句会在它的语句体执行结束时,自动关闭这些资源(这些资源是指,那些必须在程序结束时,显示关闭的资源)
为了保证try语句可以正常关闭资源,这些资源类必须实现 AutoCloseable 或 Closeable 接口,实现其中的close()方法
这两个接口差不多,区别在于
AutoCloseable 可以抛出Exception异常
Closeable 最高只能抛出 IOException异常(输入输出流 根异常)
Java 7几乎把所有的“资源类”(包括文件IO的各种类、JDBC编程的Connection、Statement等接口……)进行了改写,改写后资源类都实现了AutoCloseable或Closeable接口。
8.3 Checked异常和Runtime异常体系
Java的异常被分为两大类:Checked异常和Runtime异常(运行时异常)
所有RuntimeException类及其子类的是咧被称为Runtime异常,除此之外,都叫Checked异常
并且java认为,所有的Checked异常都可被修复,所以必须处理所有的Checked异常,java才能通过编译,
对于Checked,有两种处理方式
在当前方法可以处理,那么在 try……catch块中解决异常
在当前方法不能处理异常,那么将异常抛出,给主调函数(用throws)
对与java所规定的三种异常
1、Error是程序,或者我们程序员无法控制的,错误,所以一般对此不管
2、Runtime,运行时的异常,一般不用主动去捕获
3、Checked,一种比较繁琐的异常,java要求必须处理,程序一般会选择向上抛,但这样做会出现问题,如果这个抛出Checked 的类,被复用,被继承,然后这个异常将会在程序中,蔓延开来,让程序疯狂的变的复杂,那么一般来说,最好在Checked出现的位置,就马上解决
8.3.1 使用throws声明抛出异常
如果不知道如何处理异常,可以在方法签名上写 throws 异常名 将异常抛出,交给调用者
如果main方法,也不知道如何处理这个异常,那么还可以再抛,将该异常交给JVM处理,
JVM处理的方法就是,打印异常的跟踪栈信息,并中止程序运行
throws可以抛出多个异常,每个异常类中间用逗号隔开
这里假设一个方法抛出 IOExdception,那么如果在另一方法中调用该方法,有两种选择
1.在try……catch块中调用(解决该异常)
2.继续抛出IOException异常
如果选择继续抛出,那么限制为:
2.1子类抛出的异常,不能大于父类异常,不能多余父类异常
CheckedException异常
缺点:该异常必须被处理,在继承关系中,还会限制子类方法
优点:能在编译时期,就提示程序员,程序可能存在的问题
所以作者建议,尽量不要使用CheckesException异常,而使用RuntimeException
在C#,Ruby,Python中,所有的异常都是Runtime异常
8.4 使用throw抛出异常
可以使用throw手动抛出异常,
例如:
throw new Exception(”错误提示“)
如果手动抛出Checked异常,那么有两种选择
1.在try……catch块中
2.抛出异常的方法,有throws,继续抛
如果手动抛出Runtime异常,那么,无需管它,不需要在方法的签名上写 throws,也无需放在try……catch块中,可以完全不用管它
8.4.2 自定义异常类
自定义Runtime异常,需要继承 RuntimeException基类,一般需要提供两个构造器,一个无参构造器,一个带字符串的构造器,输出异常的描述信息
8.4.3 catch和throw同时使用
异常有两种处理方式,已经提过很多次了
1.在出现异常的方法内,捕获并处理,调用者不会再捕获异常
2.在方法签名处抛出异常,将处理过程交给调用者
但在现实中,一般来说,一个异常需要多个方法协同协作,才能处理,也就是将上面两种方法结合
实现这种,多个方法协作,处理同一个异常的情形,可以使用catch块中,结合throw语句实现
再catch语句块中,用 throw new 异常 的格式,继续抛出异常
这里的原理是这样的,先在本方法中,用try……catch捕获异常,处理一部分,再catch块中,继续抛出异常,在方法签名上也继续抛,交给调用者继续处理
8.4.4 Java 7增强的throw语句
在java7时,对throw做了增强,可以识别抛出的异常大小,如捕获时,声明的异常为Exception,但
实际可能只抛出FileNotFoundException,那么在方法签名处,可抛出文件找不到异常
也就是说,可以抛出比声明的异常类型更小的异常
8.4.5 异常链
如果在用户操作时,出现异常,那么这时,不应该将底层的异常直接展示给用户,而应该先捕获异常,然后抛出一个新的业务异常,其中包含对用户的提示信息,这种处理方式被称为异常转译
通常的做法是,在catch块中,保存原始异常,然后抛出别的异常,给用户提示,原始的异常保存下来,留给管理员,
这种捕获一个异常,然后抛出另一异常,并把原始异常信息保存下来,是一种典型的链式处理,在23中设计模式中,称为职责链模式,也称为 异常链
在JDK1.4时,程序员必须自己写代码,来接受原始异常,但在JDK1.4之后,可以将原始异常,传递给新的异常,也就是说,即使在当前位置创建并抛出了新的异常,也能通过这个新的异常追踪到异常最初发生的位置,
格式:
在catch块中,
catch(Exception e){
throw new 异常(e)
}
用throw抛出的,是我们新new的异常,而调用构造器时,放的实参 e ,就是原始异常
8.5 Java的异常跟踪栈
异常对象的 printStackTrace()方法用于打印异常的跟踪栈信息, 根据她的输出,我们可以找到异常的源头,并一路跟踪异常的触发过程
在面向对象的编程中,大多数复杂的操作会被分解成一系列方法的调用,这是为了更好的复用性,所以会形成,方法调用栈
这里请注意异常的传播,如果异常没有被完全捕获,那么这个异常会开始扩散,尤其是,抛出异常的类被大量复用,那么这个类所带的异常也会快速开始传播
在最后需要发布程序时,应该尽量避免使用异常跟踪栈
8.6 异常处理规则
异常处理的4个目标
1.时程度代码混乱最小化
2.捕获并保留诊断信息
3.通知合适的人员
4,采用合适的方式结束活动
8.6.1 不要过度使用异常
只对于,外部的,不确定的,和预知的运行错误时,才使用异常
异常处理机制的初衷是将不可预期异常的处理代码和正常的业务逻辑处理代码分离,因此绝不要使用异常处理来代替正常的业务逻辑判断。另外,异常机制的效率比正常的流程控制效率差
8.6.2 不要使用过于庞大的try块
如果使用try包裹很大的一段代码,那么try块中出现异常的可能性会大大提高,所以导致try下的catch块也变的庞大,并且逻辑也变的复杂,
正确的做法是,把庞大的try块,切割为多个可能出现异常的,小try块
8.6.3 避免使用Catch All语句
在catch块上,使用
catch(Throwable t){
//异常处理
t.printStackTrace();
}
这中异常处理方式,称为 Catch All
好处:避免错误处理,加快编程速度(我有预感以后会经常使用)
坏处:1.无法对异常分情况处理,
2.压制异常,因为它会捕获程序中的Error,Runtime异常等,但也有可能会压制一些非常关键的异常信息
8.6.4 不要忽略捕获到的异常
设想一种情形,如果做了try块,也捕获了异常,但在异常处理时,什么都不写
那么,当程序出错时,不会有任何异常提示,也不知道那里出了问题,但程序已经彻底崩了
所以在catch块中,应该对异常采取措施
1.处理异常,进行合适的修复
2.重新抛出新的异常,做异常转译,向上抛
3.直接抛出,在方法签名处使用throws
本章小结
关于异常的关键字
try (包裹可能出现异常的代码)
catch(捕获异常)
finally(类似switch的 default ,但finally块中的代码一定会被执行 )
throw(手动抛出一个具体类型的异常)
throws(在方法的签名处,声明一个方法可能出现的所有异常)
尾声