Java 异常机制
一.Java 异常机制
—— Java的异常机制是将异常处理代码和正常业务代码分开,保证程序代码的可读性及增强程序的健壮性(鲁棒性)。
二.Java 的异常处理流程
基本异常处理流程如下:
try // 执行业务处理,若完全执行,则一切正常
{
//正常业务处理代码
}
catch (异常1 ex1)
{
// 进行异常1处理
}
catch (异常2 ex2)
{
// 进行异常2处理
}
……
catch (异常n exn)
{
// 进行异常n处理
}
说明:
① 当执行try 代码块时出现异常,系统会自动生成一个异常对象 ex ;
② 依次用异常对象匹配catch 块异常类(用 instanceof 进行匹配);
③ 若果 instanceof 运算返回true ,系统会自动将异常对象传给 catch 块后面的形参,并执行 catch 块中的代码。
★ 不同的 catch 块负责不同的异常处理,对于一个异常,最多只有一个 catch 块能捕捉到该异常;
★ 多个 catch 块捕捉异常,应该先捕捉子类异常(小异常),再捕捉父类异常(大异常);
★ JDK 1.7 提供了新功能:多异常捕捉
catch (类1 |类2 | … ex )
★ 访问异常对象:
— 当catch 块捕捉到异常时,异常对象会作为 catch 块的参数被传入,因此 catch 块可通过该参数来访问实际的异常对象。
三.Java 异常类的继承体系
Java 提供了丰富的异常类,这些类有着严格的继承体系,如下图 :
从图可以看出,Java 把所有非正常情况分为两种:异常(Exception)和错误(Error),二者都继承了 Throwable 父类(Error 错误,一般是指虚拟机JVM的相关问题,这种错误无法恢复或不可能捕获,将导致应用程序中断)。
四.异常处理的完整语法格式
try
{
//正常业务处理代码
}
catch (异常 ex) // 0~ N 个
{
//处理异常代码
}
Finally // 0~ 1 个
{
}
★ try 代码块不能独立存在——catch 和 finally 代码块至少要有一个
★ finally ——通常用于回收资源
JVM会保证 finally 代码块总是得到执行,无论程序是否抛出异常,即使是return 语句(return语句可以结束方法执行,并返回)也不能阻止;除非遇到退出JVM的代码,finally 代码块才不会执行。
示例程序1:
public class FinallyTest
{
public static void main(String[] args)
{
System.out.println(test());
}
public static int test()
{
int a = 10;
try
{
System.out.println("执行 try 代码块");
return ++a;
}
/* try 代码块里面的 return 语句会执行,原本执行 return 语句后,
方法会返回,但是 finally 代码块总会获得执行,所以处理流程是:
执行 try 代码块里面的 return后(此时 a= 11)方法在将要返回、而
有没有返回时,系统会去执行 finally 代码块,最后遇到 finally代码
块里面的 return 是返回(a=12)*/
finally
{
System.out.println("执行 finally 代码块");
return ++a;
}
}
}
运行结果 :
执行 try 代码块
执行 finally 代码块
12
示例程序2:退出 JVM ,finally块不执行
public class FinallyTest
{
public static void main(String[] args)
{
try
{
System.out.println("执行 try 代码块");
System.exit(1); //退出JVM,finally块不执行,也可以用下面一行代码
// Runtime.getRuntime().exit(1);
}
catch (Exception ex)
{
ex.printStackTrace(); //输出异常跟踪栈(堆栈)信息
}
finally
{
System.out.println("finally 块的执行");
}
}
}
运行结果:
执行 try 代码块
五.异常处理的嵌套
在 try块、catch 块、finally 块中包含完整的异常处理流程的情形被称为异常处理的嵌套。
异常处理代码可以放在任何能放可执行代码的地方, 因此完整的异常处理流程代码可以放在try块、catch 块、finally 块中,不过一般很少放在 try块中。异常处理的嵌套程度没有明确的限制,但通常没有必要使用超过两层的嵌套,嵌套层次过多,会降低程序的可读性。
六.try语句自动关闭资源
— JDK1.7 提供了一个自动关闭资源的try语句
try ( // 只能声明并创建可自动关闭的资源)
{
//业务处理
}
★ 对于自动关闭资源的 try 语句,可以没有 catch或finally 块而独立存在,自动关闭资源的 try 语句有两个注意点:
— 只有放在 try 后面的 () 里面的资源才会被关闭;
— try 后面的 () 里面 只能声明并创建可自动关闭的资源,创建的资源引用变量属于try代码块。
— 能被自动关闭的资源必须实现 Closeable 或者 AutoCloseable 接口。
示例程序1:
//本程序演示用finally 块手动关闭资源
import java.io.*;
import java.net.*;
public class FinallyCloseTest
{
public static void main(String[] args)
throws Exception
{
FileInputStream fis = null; //声明资源(资源变量 fis)
try
{
//资源为“Test.java”,即本程序文件
fis = new FileInputStream("Test.java");
//fis.read()读入加一个字符
System.out.println((char)fis.read());
System.out.println((char)fis.read());
System.out.println((char)fis.read());
System.out.println((char)fis.read());
System.out.println((char)fis.read());
System.out.println((char)fis.read());
//以上业务代码读入“Test.java”文件(即本程序)
//的前六个字符并打印,及“i,m,p,o,r,t”
}
finally
{
try
{
fis.close(); //关闭文件
}
catch (Exception ex)
{
ex.printStackTrace();
}
}
}
}
运行结果:
i
m
p
o
r
t
示例程序2:
//本程序演示 try 块自动关闭资源
import java.io.*;
import java.net.*;
public class AutoCloseTest
{
public static void main(String[] args)
throws Exception
{
try(FileInputStream fis =
new FileInputStream("Test.java");)
//声明资源(资源变量 fis)
//资源为“Test.java”,即本程序文件
{
//fis.read()读入加一个字符
System.out.println((char)fis.read());
System.out.println((char)fis.read());
System.out.println((char)fis.read());
System.out.println((char)fis.read());
System.out.println((char)fis.read());
System.out.println((char)fis.read());
//以上业务代码读入“Test.java”文件(即本程序)
//的前六个字符并打印,及“i,m,p,o,r,t”
}
//自动关闭资源的 try块,相当于最后有一个隐藏的 finally 块
}
}
运行结果:
i
m
p
o
r
t
七.异常分类:checked异常和 runtime 异常
— checked 异常:普通的、不是RuntimeException子类的异常,都是 checked异常;
checked 异常,其设计初衷:没有完善的错误处理代码,不会得到执行的机会。对于与checked异常,编译器会强制检查,因而可以更好地保证“程序员一定会对异常进行处理”,但是给编程带来麻烦,因为编译器要求程序员:要么用 throws声明抛出(说明该方法已经注意到了异常,但目前该方法无法或者不想处理该异常),要么显示同catch捕捉。
在方法重写是,要求子类方法用 throws声明抛出的异常必须比父类方法更小或相等,应该注意的是,子类方法不声明抛出也是可以的(不声明抛出异常,可理解为异常最小)。
— runtime 异常:直接或者间接继承了 RuntimeException的子类都是 runtime 异常,否则就是checked异常。
对于runtime 异常,编译器不会强制进行检查,对于异常的的捕捉与处理与否取决于程序员。
八.异常转译和自定义异常
—— 异常是主观的,只要程序出现业务不希望的情况,就可以认为是异常。
① throws 和 throw 的区别:
(1) throws 只能在方法签名中使用,后面可以跟多个异常类的类名,多个类名之间用逗号隔开;
(2) throw 就是一条普通语句,throw 后面紧跟一个异常实例。
示例程序: throw 抛出异常
public class Test
{
public static void main(String[] args)
{
//将用户传入的第1个参数转换为double
double d1 = Double.parseDouble(args[0]);
//将用户传入的第2个参数转换为double
double d2 = Double.parseDouble(args[1]);
//主观认为第2个参数不能为0
//如果第2个参数 d2 的值为0,业务上认为是异常
if(d2 == 0)
{
throw new IllegalArgumentException("参数不能为0!");
}
}
}
运行结果:
② 抛出异常的两种可能情况:
(1) 程序不能成功地执行完成,由系统自动生成异常对象,并抛出;
(2) 程序出现了与业务规则不相符的情况,由程序员来创建异常对象,并通过 throw关键字抛出。
—— 即使是程序员通过throw 抛出的异常,如果是 checked 异常,JVM同样会强制检查,要求程序员要么显示捕捉,要么用throws 声明抛出。
③ 有名的Runtime异常:
— NullPointerException 空指针异常
— ArrayIndexOutOfBoundsException 数组索引(下标)越界异常
— ClassCastException 类型转换异常
— ArithmeticException 算数异常
— NumberFormatException 字符转数字格式(类型)异常
— IllegalArgumentException 非法参数异常
④ 异常转译:catch与throw的结合使用
在某些情况下,A方法(例如main方法)调用了B方法(例如普通方法),当B方法出现了异常,而且B方法自己捕捉了异常,此时A方法就无法判断被调用的B方法是否发生过异常。
在这种情况下,若是B方法虽然自身捕获了异常,但是A方法有需要知道被调用的B方法发生过的异常,可以采用的做法是:先用 catch 捕获原有的异常,再用 throw 抛出一个自定义异常,即 异常转译。
⑤ 自定义异常:
— 自定义 runtime 异常,继承 RuntimeException
— 自定义 checked 异常,继承 Exception
示例程序:自定义异常
File1:
//自定义一个runtime 异常类 MyException
public class MyException extends RuntimeException
{
public MyException(){} //无参构造器
public MyException(String msg)
{
// 显示调用父类 RuntimeException中带一个String参数的构造器
super(msg);
}
}
File2:
public class Test
{
public static void main(String[] args)
{
try
{
//将用户传入的第1个参数转换为int
Integer tg1 = Integer.parseInt(args[0]);
//将用户传入的第2个参数转换为int
Integer tg2 = Integer.parseInt(args[1]);
//打印 tg1/tg2 的结果
System.out.println(tg1 / tg2);
}
//捕捉数组索引越界异常
catch (ArrayIndexOutOfBoundsException ex)
{
throw new MyException("数组索引越界");
}
//捕捉字符转数字格式(类型)异常
catch (NumberFormatException ex)
{
throw new MyException("必须输入数字");
}
//捕捉算数异常
catch (ArithmeticException ex)
{
throw new MyException("除数不能为0");
}
//捕捉 Exception 应该放在后面
catch (Exception ex)
{
throw new MyException("捕捉 Exception");
}
}
}
运行结果: