异常
一、 概述
Java的异常机制主要依赖于try、catch、finally、throw和throws五个关键字
其中try关键字后紧跟一个花括号扩起来的代码块(花括号不可省略),简称ty块,它里面放置可能引发异常的代码。
catch后对应异常类型和一个代码块,用于表明该catch块用于处理这种类型的代码块。多个catch块后还可以跟一个finally块
finally块用于回收在try块里打开的物理资源,异常机制会保证finally块总被执行。
throws关键字主要在方法签名中使用,用于声明该方法可能抛出的异常;
throw用于抛出一个实际的异常,thow可以单独作为语句使用,抛出一个具体的异常对象。
二、异常处理机制
2.1 使用try…catch捕获异常
如果执行y块里的业务逻辑代码时出现异常,系统自动生成一个异常对象,该异常对象被提交给Java运行时环境,这个过程被称为抛出(throw)异常。
当Java运行时环境收到异常对象时,会寻找能处理该异常对象的catch块,如果找到合适的catch块,则把该异常对象交给该catch块处理,这个过程被称为捕获(catch)异常;
如果Java运行时环境找不到捕获异常的catch块,则运行时环境终止,Java程序也将退出。
2.2 异常类的继承体系
流程
异常捕获时,一定要记住先捕获小异常,再捕获大异常。
2.3 多异常捕获
使用一个catch块捕获多种类型的异常时需要注意如下两个地方。
捕获多种类型的异常时,多种异常类型之间用竖线(|)隔开。
捕获多种类型的异常时,异常变量有隐式的final修饰,因此程序不能对异常变量重新赋值。
2.4 访问异常信息
2.5 使用finally回收资源
程序在try块里打开了一些物理资源(例如数据库连接、网络连接和磁盘文件等),这些物理资源都必须显式回收。Java的垃圾回收机制不会回收任何物理资隙,垃圾回收机制只能回收推内存中对象所占用的内存。
若是return ,会执行finally块 若是finally块 有return,则try块、catch块失效
若是 System.exit(1); 不会执行finally块
public class FinallyTest {
public static void main(String[] args) {
FileInputStream fis = null;
try {
fis = new FileInputStream("a.txt");
}catch (IOException io){
System.out.println(io.getMessage());
return; //1
// System.exit(1); //2
}finally {
if (fis != null){
try {
fis.close();
}catch (IOException io){
io.printStackTrace();
}
}
System.out.println("回收"); //a.txt (系统找不到指定的文件。) 回收
}
}
}
三、Checked异常和Runtime异常体系
只有Java语言提供了Checked异常,其他语言都没有提供Checked异常。Java认为Checked异常都是可以被处理(修复)的异常,所以Java程序必须显式处理Checked异常。如果程序没有处理Checked异常,该程序在编译时就会发生错误,无法通过编译。
Checked异常:要么显式声明抛出,要么显式捕获并处理它
对于Checked异常的处理方式有如下两种。
当前方法明确知道如何处理该异常,程序应该使用try.catch块来捕获该异常,然后在对应的catch块中修复该异常。
当前方法不知道如何处理这种异常,应该在定义该方法时声明抛出该异常。
Runtime异常则更加灵活,Runtime异常无须显式声明抛出,如果程序需要捕获Runtime异常,也可以使用try.catch块来实现。
3.1 使用throws抛出异常
使用throws声明抛出异常的思路是,当前方法不知道如何处理这种类型的异常,该异常应该由上一级调用者处理:如果main方法也不知道如何处理这种类型的异常,也可以使用throws声明抛出异常,该异常将交给JVM处理。JVM对异常的处理方法是,打印异常的跟踪栈信息,并中止程序运行,这就是前面程序在遇到异常后自动结束的原因。
使用throws声明抛出异常时有一个限制,就是方法重写时“两小”中的一条规则:子类方法声明抛出的异常类型应该是父类方法声明抛出的异常类型的子类或相同,子类方法声明抛出的异常不允许比父类方法声明抛出的异常多。
public class ThrowsTest {
static void test() throws IOException {
FileOutputStream fis = new FileOutputStream("a.txt");
}
public static void main(String[] args) throws Exception{
test(); //test()声明抛出了IOException异常,所以调用也要声明,要么throws,要么位于try..catch中
}
}
四、使用throw抛出异常
如果需要在程序中自行抛出异常,则应使用throw语句,throw语句可以单独使用,throw语句抛出的不是异常类,而是一个异常实例,而且每次只能抛出一个异常实例。
如果throw语句抛出的异常是Checked异常,则该throw语句要么处于try块里,显式捕获该异常,要么放在一个带throws声明抛出的方法中,即把该异常交给该方法的调用者处理
如果throw语句抛出的异常是Runtime异常,则该语句无须放在try块里,也无须放在带throws声明抛出的方法中:程序既可以显式使用try…catch来捕获并处理该异常,也可以完全不理会该异常,把该异常交给该方法调用者处理。
4.1 自定义异常类
用户自定义异常都应该继承Exception基类,如果希望自定义Runtime异常,则应该继承RuntimeException基类。定义异常类时通常需要提供两个构造器:一个是无参数的构造器:另一个是带一个字符串参数的构造器,这个字符串将作为该异常对象的描述信息(也就是异常对象的getMessage()方法的返回值)。
public class AuctionException extends Exception{
public AuctionException(){};
public AuctionException(String msg){
super(msg);
};
}
4.2 catch和throw同时使用
public class AuctionTest {
private double initPrice = 30.0;
public void bid(String b) throws AuctionException { //catch块显示抛出AuctionException,需要声明显示抛出AuctionException
double d = 0.0;
try {
d = Double.parseDouble(b);
} 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 e) {
System.out.println(e.getMessage());
}
}
}
4.4 异常链
把底层的原始异常直接传给用户是一种不负责任的表现。通常的做法是:程序先捕获原始异常,然后抛出一个新的业务异常,新的业务异常中包含了对用户的提示信息,这种处理方式被称为异常转译。假设程序需要实现工资计算的方法,则程序应该采用如下结构的代码来实现该方法。
这种把捕获一个异常然后接着抛出另一个异常,并把原始异常信息保存下来是一种典型的链式处理(23种设计模式之一:职责链模式),也被称为“异常链”。
五、异常跟踪栈
class SelException extends RuntimeException{
SelException(){}
SelException(String msg){super(msg);}
}
public class PrintstackTraceTest {
public static void main(String[] args) {
{
first();
}
}
private static void first() {
second();
}
private static void second() {
third();
}
private static void third() {
throw new SelException("自定义");
}
}
六、异常处理规则
6.1 不要过度使用异常
异常处理机制的初衷是将不可预期异常的处理代码和正常的业务逻辑处理代码分离,因此绝不要使用异常处理来代替正常的业务逻辑判断。
另外,异常机制的效率比正常的流程控制效率差,所以不要使用异常处理来代替正常的程序流程控制。
6.2 不要使用过于庞大的try块
而且当try块过于庞大时,就难免在try块后紧跟大量的catch块才可以针对不同的异常提供不同的处理逻辑。同一个try块后紧跟大量的ctch块则需要分析它们之间的逻辑关系,反而增加了编程复杂度。
正确的做法是,把大块的try块分割成多个可能出现异常的程序段落,并把它们放在单独的try块中,从而分别捕获并处理异常。
6.3 避免使用Catch All语句
所谓Catch All语句指的是一种异常捕获模块,它可以处理程序发生的所有可能异常。例如,
try
//可能引发Checked异常的代码
catch (Throwable t)
//进行异常处理
t.printstackTrace();
这种处理方式有如下两点不足之处。
所有的异常都采用相同的处理方式,这将导致无法对不同的异常分情况处理,如果要分情况处理,则需要在catch块中使用分支语句进行控制。
.
这种捕获方式可能将程序中的错误、Runtime异常等可能导致程序终止的情况全部捕获到,从而“压制”了异常。如果出现了一些“关键”异常,那么此异常也会被“静悄悄”地忽略。