文章目录
重点概述
- Java异常机制主要依赖于try、catch、finally、throw和throws五个关键字。
- throws关键字主要在方法签名中使用,用于声明该方法可能抛出的异常;throw用于抛出一个实际的异常,可以单独作为语句使用,抛出一个具体的异常对象。
- Java将异常分为两种:Checked异常和Runtime异常。
Java认为Checked异常都是可以在编译阶段被处理的异常,所以他强制程序处理所有的Checked异常;而Runtime异常则无须处理。
一、异常处理机制
1.使用try…catch捕获异常
抛出异常:如果执行try块里的业务逻辑代码时出现异常,系统自动生成一个异常对象,该异常对象被提交给Java运行时环境。
捕获异常:当Java运行时环境收到异常对象时,会寻找能处理该异常对象的catch块,如果找到合适的catch块,则把该异常对象交给该catch块处理。
如果Java运行时环境找不到捕获异常的catch块,则运行时环境终止,Java程序也将退出。
不管程序代码块是处在什么位置,(甚至在catch块中),只要执行该代码块出现了异常,系统总自动生成一个异常对象。
2.异常类的继承体系
如果try块被执行一次,则try块后只有一个catch块会被执行,绝不可能有多个catch块会被执行。
try块里声明的变量是代码块局部变量,它只在try块内有效,在catch块中不能访问该变量。
Java常见类的继承关系如下图:
Error错误,一般是指与虚拟机相关的问题,如系统崩溃、虚拟机错误等,这种错误无法恢复或不可能捕获,错误会导致应用程序中断。通常应用程序无法处理这些错误,因此应用程序不应该试图使用catch块来捕获Error对象。也无须在定义方法时,使用throws抛出Error。
进行异常捕获时,所有父类异常的catch块都应该排在子类catch块后面,否则将出现编译错误。(先处理小异常,再处理大异常)
3.使用finally回收资源
程序在try里打开的物理资源,如数据库连接、网络连接和磁盘文件等,都需要显示回收。
Java的垃圾回收机制不会回收任何物理资源,只能回收堆内存中对象所占用的内存。
finally块总会被执行,甚至try或catch中执行了return语句。特殊情况:如果使用了System.exit(1)退出虚拟机,则finally块不会执行了。
try块是必须的,catch块和finally块至少出现其中之一,finally块必须位于所有的catch块之后。
一旦在finally块中使用了return或throw语句,将会导致try块、catch块中的return、throw语句失效。(因此尽量避免这种情况)
如果try块中return了一个值,这个返回值会保存下来 。即使finally对这个值又修改了,最后还会返回之前保存的值(finally没有return,否则finally的return会覆盖try块中的return)。如果return一个引用类型,finally中可能会改变return的值。
finally块是在try块的return语句执行之后,return语句返回之前执行。
4.Java7自动关闭资源的try语句
Java7允许在try关键字后申明初始化多个资源,这些资源必须是在程序结束时显示关闭的资源。try语句在该语句结束时自动关闭这些资源。(这些资源必须实现AutoCloseable或Closeable接口)
示例如下:
try(
BufferedReader br = new BufferedReader(new FileReader("Test.java"));
//最后一个资源结束时不用分号
PrintStream ps = new PrintStream(new FileOutputStream("a.txt"))
)
{
ps.println("hello world");
}
自动关闭资源的try语句相当于包含了隐式的finally块,因此这个ty语句可以既没有catch块也没有finally块。如果需要自动关闭资源的try后面也可以接多个catch和finally。
二、Checked异常和Runtime异常体系
所有RuntimeException类及其子类的实例被称为Runtime异常。其他的就是Checked异常。
Checked异常:IOException,SQLException
如果程序没有处理Checked异常,那在编译时就会发生错误,无法通过编译。
Checked异常要么显示声明抛出,那么显示捕获并处理它。
1.使用throws声明抛出异常
当前方法不知道如何处理这种异常,就往上一级抛,如果都不处理,一直抛出去。最终会由JVM处理。JVM会中止程序运行。
throws声明抛出只能在方法签名中使用,throws可以抛出多个异常类,用逗号隔开。
一旦使用throws语句声明抛出该异常后,就无须使用try…catch块来捕获该异常了。
如果一个方法声明用throws抛出Checked异常,则调用该方法时要么放在try块中显示捕获该异常,要么调用方法也用throws抛出异常。
使用throws声明的方法,重写时有限制:子类方法声明抛出的异常类型应该是父类方法声明抛出的异常类型的子类或者相同。
大部分时候推荐使用Runtime异常,而不用Checked异常。因为Checked异常必须处理,增加编程复杂度;而且如果是Checked异常,方法不处理的时候就必须在签名中用throws抛出该Checked异常,这样就导致方法签名与异常耦合,重写时有限制。
2.使用throw自行抛出异常
throw语句可以单独使用,抛出一个异常实例,而且只能是一个异常实例。
当Java运行时接收到开发者自行抛出的异常时,同样会中止当前的执行流,跳到该异常对应的catch块,由该catch块来处理该异常。
无论是throws在方法签名中抛出异常类,还是throw抛出异常实例,只要抛出的是Checked异常,那么调用方法时,要么处于try块中,要么还用throws继续抛出异常。
3.自定义异常类
继承Exception基类(如果是自定义Runtime异常,则继承RuntimeException基类);提供一个无参构造器,和一个带字符串参数的构造器。(字符串作为异常对象的描述信息,就是getMessage())
示例代码如下:
publc class CustomException extends Exception{
public CustomException(){}
public CustomException(String msg){
super(msg);
}
}
4.Java7增强的throw语句
Java7开始对异常检查更细致了,Java编译器会检查throw语句抛出异常的实际类型。如下:
public void test() throws FileNotFoundException{
try{
...
new FileOutputStream("a.txt");
}catch(Exception e){
//e只可能是FileNotFoundException,因此在方法签名中抛出FileNotFoundException异常即可。Java7之前需要抛出Exception异常
throw e;
}
}
5.异常链
捕获一个异常然后接着抛出另一个异常,并把原始异常信息保存下来,是典型的链式操作。
6.访问异常信息
异常对象的方法:
- getMessage():返回该异常的详细描述字符串
- printStackTrace():将该异常的跟踪栈信息输出到标准错误输出
- printStackTrace(PrintStream s):输出到指定输出流。
- getStackTrace():返回该异常的跟踪信息。
printStackTrace()在最后发布的程序中应该去掉。