- 1 Java异常处理机制
- 1.1 概述
- 1.2 声明异常
- 1.3 抛出异常
- 1.3.1 使用throw抛出异常
- 1.3.2 自定义异常类
- 1.3.3 catch和throw同时使用
- 1.3.4 异常链
- 1.4 捕获异常
- 1.4.1 使用try…catch捕获异常
- 1.4.2 多异常捕获
- 1.5 异常的处理
- 1.6 资源回收
- 1.6.1 使用finally回收资源
- 1.6.2 自动关闭资源
- 1.7 空指针异常
- 1.8 使用异常机制的技巧
- 2 断言
1 Java异常处理机制
1.1 概述
在Java中,异常对象都是派生于Throwable
类的一个实例,但是在下一层立即分解为两个分支:Error
和Exception
。JVM处理异常的方法是——打印异常栈的跟踪信息,并终止代码的执行。
表1-1 异常类的继承体系
-
Error
类层次结构描述了Java运行系统的内部错误和资源耗尽错误,应用程序不应该抛出这种类型的对象,如果出现了这样的内部错误,除了通告给用户,并尽力使程序安全地终止之外,再也无能为力了。 - 在设计Java程序时,需要关注
Exception
层次结构,这个层次结构中分解为两个分支:
- 一个分支派生于
RuntimeException
,由程序错误导致的异常
- 错误的类型转换
- 数组访问越界
- 访问
null
指针
- 另一个分支包含其他异常,包括
IOException
和SQLException
等,程序本身没有问题,而由于IO
错误导致异常。
- 试图在文件尾部后面读取数据;
- 试图打开一个不存在的文件;
- 试图根据给定的字符串查找
Class
对象,而这个字符串表示的类并不存在
- 异常类型:Java语言规范将派生于
Error
类或RuntimeException
类的所有异常称为Runtime
异常,所有其它的异常称为Checked
异常,编译器将核查是否为所有的Checked
异常提供了异常处理器。 Checked
异常的处理方式:
- 知道如何处理异常,那么使用try…catch代码块来捕获处理
- 不知道如何处理的,在定义方法的时候声明抛出异常,交给上一级处理
- 常见异常:
- IndexOutOfBoundsException:数组访问越界
- NumberFormatException:参数形式不正确
- ArithmeticException:算术运算异常
- NullPointerExceptn:空指针异常
- 异常五小强:
try
、catch
、throw
、throws
、finally
- try:里面放置可能引起异常的代码
- catch:用于处理申明的异常类型
- finally:用于回收资源,总是被执行
- throw:抛出一个具体的异常
- throws:声明方法可能抛出的异常
1.2 声明异常
- 一个方法应该在其首部声明所有可能抛出的异常,这样可以从首部反映出这个方法可能抛出哪类受查异常,如果一个方法有可能抛出多个受查异常类型,那么就必须在方法的首部列出所有的异常类,每个异常类之间用逗号隔开。
- 使用throws声明抛出异常:
- 语法格式:
修饰符 方法名(参数) throws 异常1,异常2,... {}
- 限制:子类方法声明抛出的异常类型应该是父类方法声明抛出的异常类型的子类或相同,子类方法声明抛出的异常不允许比父类方法声明抛出的异常多
- 四种需要抛出异常的情景:
- 调用一个抛出受查异常的方法
- 程序运行过程中发现错误,并且利用
throw
语句抛出一个受查异常 - 程序出现错误:数组访问越界
- JVM和运行库出现的内部错误
1.3 抛出异常
- 抛出异常的步骤:
- 找到一个合适的异常类
- 创建这个类的一个对象
- 将对象抛出
1.3.1 使用throw抛出异常
如果需要在程序中自行抛出异常,则应使用throw
语句,throw
语句可以单独使用,throw
语句抛出的不是异常类,是一个异常实例,而且每次只能抛出一个实例。
语法格式:
throw ExceptionInstance;
如果抛出的异常是Checked
异常,则该throw
语句要么处于try
块,显式捕获该异常,要么放在一个带throws
声明抛出的方法中,即把该异常交给方法的调用者处理;如果throw
语句抛出的是Runtime
异常,则自由的很,可以处理,也可以不处理
1.3.2 自定义异常类
用户自定义异常都应该继承Exception
基类,如果希望自定义Runtime
异常,则应该继承RuntimeException
基类,定义异常类的时候通常需要提供两个构造器:一个是无参数的构造器,一个是带一个字符串参数的构造器,这个字符串将作为该异常对象的描述信息。
public class AuctionException extends Exception {
// 无参数的构造器
public AuctionException(){}
// 带一个字符串参数的构造器
public AuctionException(String msg) {
super(msg);
}
}
1.3.3 catch和throw同时使用
- 配合自定义异常类的使用
public class AuctionTest {
private double initPrice = 30.0;
// 因为该方法中显式抛出了AuctionException异常,
// 所以此处需要声明抛出AuctionException异常
public void bid(String bidPrice) throws AuctionException {
double d = 0.0;
try {
d = Double.parseDouble(bidPrice);
}
catch (Exception e) {
// 此处完成本方法中可以对异常执行的修复处理,
// 此处仅仅是在控制台打印异常跟踪栈信息。
e.printStackTrace();
// 再次抛出自定义异常
throw new AuctionException("竞拍价必须是数值," + "不能包含其他字符!");
}
if (initPrice > d) {
throw new AuctionException("竞拍价比起拍价低," + "不允许竞拍");
}
initPrice = d;
}
public static void main(String[] args) {
AuctionTest at = new AuctionTest();
try {
at.bid("df");
}
catch (AuctionException ae) {
// 再次捕捉到bid方法中的异常。并对该异常进行处理
System.err.println(ae.getMessage());
}
}
}
1.3.4 异常链
- 异常转译:程序先捕获原始异常,然后抛出一个新的业务异常,新的业务中包含了对用户的提示信息
- 异常链:捕获一个异常然后接着抛出另一个异常,并把原始异常信息保存下来
- Java的异常跟踪栈:就是找到异常触发的源头和一路触发的过程
1.4 捕获异常
- 要想捕获一个异常,必须设置
try/catch
语句块,如果在try
语句块中的任何代码抛出了一个在catch
子句中说明的异常类,那么:
- 程序将跳过
try
语句块的其余代码 - 程序将执行
catch
子句中的处理器代码
- 如果在
try
语句块中的代码没有抛出任何异常,那么程序将跳过catch
子句 - 异常捕获时,先捕获处理小异常,再捕获处理大异常。
1.4.1 使用try…catch捕获异常
- Java异常处理机制的语法结构:
try {
//业务实现代码...
}
catch(Exception e) {
//异常处理代码...
}
- 抛出(throw)异常:异常对象产生并提交给Java运行时环境
- 捕获(catch)异常:把合适的异常对象提交给对应的catch块处理
1.4.2 多异常捕获
- Java多异常处理机制的语法结构:
try{
//业务实现代码...
}
catch(Exception1|Exception2|…|Exceptionn e){
//异常处理代码...
}
- 注意:
- 捕获多种异常时,多种异常类型之间用(
|
)隔开 - 捕获多种异常时,异常变量有隐式的
final
修饰,因此程序不能对异常变量重新赋值 - 只有当捕获的异常类型彼此之间不存在子类关系时才需要这个特性。
1.5 异常的处理
- 访问异常信息常用方法:
- getMessage():返回该异常的详细描述字符串
- printStackTrace():将该异常的跟踪栈信息输出到标准错误输出
- printStackTrace(PrintStream s):将该异常的跟踪栈信息输出到指定输出流
- getStackTrace():返回该异常的跟踪栈信息
ii. 异常处理的嵌套
- 注意:没必要使用超过两层的嵌套
1.6 资源回收
1.6.1 使用finally回收资源
- 为了保证一定能回收
try
块打开的物理资源,异常处理机制提供了finally
块:
try{
//业务实现代码...
}
catch(Exception1|Exception2|…|Exceptionn e){
//异常处理代码...
}
finally{
//回收资源
}
- 注意:
-
return
语句不会打断finally
的处理,但是System.exit()
函数可以打断不执行finally
块 - 一旦在
finally
块中使用了return
或throw
语句,将会导致try
块、catch
块中的return
、throw
语句失效
1.6.2 自动关闭资源
- 增强的try语句:
try
关键字后紧跟一个圆括号,圆括号可以声明、初始化一个或多个资源,此处的资源指的是那些必须在程序结束时显式关闭的资源,try
语句在该语句结束时自动关闭这些资源。这些资源必须实现AutoCloseable
或Closeable
接口,Java 7几乎所有的资源类都实现了AutoCloseable
或Closeable
接口,推荐还是使用finally语句。
public static void main(String[] args) throws IOException {
try (
// 声明、初始化两个可关闭的资源
// try语句会自动关闭这两个资源。
BufferedReader br = new BufferedReader(new FileReader("AutoCloseTest.java"));
PrintStream ps = new PrintStream(new FileOutputStream("a.txt"))
) {
// 使用两个资源
System.out.println(br.readLine());
ps.println("庄生晓梦迷蝴蝶");
}
}
1.7 空指针异常
- NullPointerException:
java.util.Optional<T>
,变量存在时,Optional
类只是对类简单封装,变量不存在时,缺失的值会被建模成一个“空”的Optional
对象,由方法Optional.empty()
返回。
import java.util.Optional;
class Person {
private Optional<Car> car;
public Optional<Car> getCar() {
return car;
}
}
class Car {
private Optional<Insurance> insurance;
public Optional<Insurance> getInsurance() {
return insurance;
}
}
class Insurance {
private String name;
public String getName() {
return name;
}
}
public class OptionalTest {
public static void main(String[] args) {
Car car = new Car();
//申明一个空的Optional
Optional<Car> car1 = Optional.empty();
//依据一个非空值创建Optional
Optional<Car> car2 = Optional.of(car);
//可接受null的Optional
Optional<Car> car3 = Optional.ofNullable(car);
Insurance example = new Insurance();
Optional<Insurance> insurance = Optional.ofNullable(example);
//使用map从Optional对象中获取和转换值
Optional<String> name = insurance.map(Insurance::getName);
}
}
1.8 使用异常机制的技巧
- 异常处理的目标:
- 使程序代码混乱最小化
- 捕获并保留诊断信息
- 通知合适的人员
- 异常处理的规则:
- 不要过度使用异常:异常处理的效率很低的
- 不要使用过于庞大的
try
块:切成小块,各自处理 - 避免使用
Catch All
语句:不能知道错误发生的原因 - 不要忽略捕获到的异常:要处理不能忽视
- 异常处理不能代替简单的测试
- 不要过分地细化异常
- 利用异常层次结构
- 不要压制异常
- 在检测错误时,“苛刻”要比放任更好
- 不要羞于传递异常
2 断言
Java的断言机制允许在测试期间向代码中插入一些检查语句,当代码发布时,这些插入的检测语句将会被自动移走,Java语言引入了关键字assert
,这个关键字有两种使用形式,这两种形式都会对条件进行检测,如果结果为false
,则抛出一个AssertionError
异常,在第二种形式中,表达式将被传入到AssertionError
的构造器,并转换成一个消息字符串。通过使用断言可以实现参数检查和文档假设的目的。
assert 条件;
assert 条件:表达式;