一、什么是异常
异常是一种对象,表示阻止正常进行程序执行的错误或者情况,包括数组下标越界、除数为零、网络连接失败、非法参数、空指针异常、文件找不到等。
二、异常的种类
这些异常类可分为三种主要类型:系统错误、异常和运行时异常。
系统错误(system error)
由Java虚拟机抛出,用Error类表示。Error类描述的是内部系统错误。这样错误很少发生。如果发生,除了通知用户以及尽量稳妥地终止程序外,几乎什么也不能做。
如:
LinkageError - 一个类对另一个类有某种依赖性,但是编译前者在后,后者进行了修改,变得不兼容
VirtualMachineError - Java虚拟机崩溃,或者运行所需的资源已经耗尽
异常(exception)
用Exception类表示,描述的是由程序和外部环境所引起的的错误,这些错误能被程序捕获和处理。
如:
ClassNotFoundException - 试图调用不存在的类
IOException - 同输入/输出有关的操作,如无效输入、读文件超过文件尾、打开一个不存在的文件等。
运行时异常(runtime exception)
用RuntimeException类表示,描述的是程序设计错误,通常由Java虚拟机抛出。
常见的有:
NullPointerException - 空指针引用异常
ClassCastException - 类型强制转换异常
IllegalArgumentException - 传递非法参数异常
ArithmeticException - 算术运算异常(一个整数除以0,浮点数的算数运算不抛出异常)
ArrayStoreException - 向数组中存放与声明类型不兼容对象异常
IndexOutOfBoundsException - 下标越界异常
NegativeArraySizeException - 创建一个大小为负数的数组错误异常
NumberFormatException - 数字格式异常
SecurityException - 安全异常
UnsupportedOperationException - 不支持的操作异常
免检异常与必检异常
RuntimeException、Error以及它们的子类都称为免检异常(unchecked exception);所有其他的异常都称为必检异常(checked exception),意思是必须通过try-catch块处理它们,或在方法头中进行声明。
大多数情况下,免检异常都会反映出程序设计上不可恢复的逻辑错误。是程序中必须纠正的逻辑错误,但免检异常可能在程序中的任何一个地方出现,为避免过多的使用try-catch块,Java语言不强制要求编写代码捕获或者声明免检异常。
三、异常处理模型
Java的异常处理基于三种模型:声明一个异常,抛出一个异常,捕获一个异常。
1、声明
每个方法都必须在方法头声明它可能抛出的必检异常的类型,这称为免检异常。
在方法中声明一个异常,在方法头中要用到关键字throws
public void myMethmod() throws IOException
如果该方法可能抛出多个异常,就可以在关键字throws后添加一个用逗号分隔的异常列表
public void myMethmod()
throws Exception1,Exception2,...,ExceptionN
attention:如果方法在父类中没有声明异常,那么就不能在子类中对其进行继承来声明异常。
2、抛出
检测到错误的程序可以创建一个合适的异常类型的实例并抛出它,这就称为抛出一个异常。
例如程序发现传递给方法的参数与方法的合约不符,这时可创建IllegalArgumentException的一个实例并抛出它,如下所示:
IllegalArgumentException ex = new IllegalArgumentException("wrong Argument");
throw ex;
或者,根据你的偏好,也可以使用下面的语句
throw new IllegalArgumentException("wrong Argument");
attention:声明异常的关键字是throws,抛出异常的关键字throw。
3、捕获
当声明和抛出异常后,可以在try-catch中捕获和处理它
try{
statements;//maybe throw exceptions
}catch(Exception1 exVar1){
hanlder for Exception1;
}catch(Exception2 exVar2){
hanlder for Exception3;
}
...
catch(ExceptionN exVarN){
hanlder for ExceptionN;
}
1.如果执行try块中没有出现异常,则跳过catch子句。
2.如果执行try块中某个语句抛出异常,Java就会跳过try块中的剩余语句,然后开始查找处理这个异常代码的过程。从第一个到最后一个逐个检查catch块,判断在catch块中是否是该异常对象的类型,如果是,就将该异常对象赋值给所声明的变量,然后执行catch块中的代码。如果执行完最后一个catch还没有发现异常处理器,那么就会退出这个方法,把此异常传递给调用这个方法的方法,继续进行同样的过程来查找异常处理器。如果在调用方法链都找不到异常处理器,程序就会在控制台上打印出出错信息。
寻找异常处理器的过程被称为捕获一个异常。
如果一个catch块可以捕获一个父类的异常对象,它就能捕获那个父类的所有子类的异常对象。
catch块中异常被指定的顺序是非常重要的。如果父类的catch出现子类的catch块之前,就会导致编译错误。
//错误的代码
try{
...
}catch(Exception ex){
...
}catch(RuntimeException ex){
...
}
//正确的代码
try{
...
}catch(RuntimeException ex){
...
}catch(Exception ex){
...
}
4、从异常中获取信息
可以用java.lang.Throwable类中的实例方法获取有关异常的信息
5、finally子句
无论异常是否产生,finally子句总是会被执行的
//正确的代码
try{
statements;
}catch(TheException ex){
handling ex;
}finally{
finalStatements;
}
注意:如果try块中无异常,执行finalStatements,然后执行try语句下一条语句。如果有一条语句引起异常,并且被catch块捕获,然后跳过try块其它语句,执行catch块和finally子句,执行try语句下一条语句;若没有被catch捕获,就会跳过try块中的其他语句,执行finally子句,并且将异常传递给这个方法的调用者。即使在到达finally块之前有一个return语句,finally块还是会执行。
异常处理器需要初始化新的异常对象,需要从调用栈返回,而且还需要沿着方法调用链来传播异常以便找到他的异常处理器,即异常处理器通常需要更多的时间和资源。
不要把异常处理用作简单的逻辑测试,即不要用try-catch处理简单的、可预料的情况。
6、重新抛出异常
如果异常处理器不能处理一个异常,或者只是简单的希望它的调用者注意到该异常,Java允许该异常处理器重新抛出异常。
try {
statements
}catch(TheException ex){
perform operations before exits;
throw ex;
}
7、链式异常
有时候,需要将原始异常一起抛出一个新异常(带有附加信息),这称为链式异常。
8、创建自定义异常类
Java中提供相当多的异常类,尽量不要自己创建异常类。然而,如果遇到不能用预定义异常类恰当的描述问题,那就可以通过派生Exception类或其子类,来创建自己的异常类。
//demo
public class InvaildRadiusExpetion extends Exception{
private double radius;
public InvaildRadiusExpetion(double radius){
super("Invalid radius " + radius);
this.radius = radius;
}
public double getRadius(){
return radius;
}
}