异常处理
同大多数的编程语言一样,Java语言也包含了许多的运算符。如果大家学习过C或者C++,会发现下面介绍的各种Java的运算符都与之类似。
5.1 try-catch-finally
首先我们介绍一下异常的一些基本概念。Java异常是一个描述在代码段中发生的异常(也就是出错)情况的对象。当异常情况发生,一个代表该异常的对象被创建并在导致该错误的方法中被引发throw。另外,你需要定义一段代码来处理这一异常,首先它需要捕获catch被引发的异常。
如果我们不提供任何异常处理程序,那么异常会被Java运行时系统的默认处理程序捕获。任何不是被你程序捕获的异常最终都会被该默认处理程序处理。默认处理程序会显示一个描述异常的字符串,打印异常发生处的堆栈轨迹并终止程序。
堆栈轨迹实际显示了导致错误产生的方法调用序列。Java在运行的时候会维护一个堆栈来保存程序运行调用的方法序列。实际上,异常发生以后就是按照这个堆栈的方法序列依次呈递给这些方法,如果这些方法里面有异常处理程序就可以捕获它加以处理,否则会一直呈递到系统默认的处理程序。
我们通过下面的例子能更容易理解这个调用方法堆栈和异常的呈递机制。
Class Exc {
static void subroutine () {
int d = 0 ;
int a = 10 / d ;
}
public static void main ( String [ ] args ) {
Exc.subroutine ();
}
}
运行结果:
java.lang.ArithmeticException: / by zero
at Exc.subroutine(Exc.java:4)
at Exc.main(Exc.java:7)
尽管由Java运行时系统提供的默认异常处理程序对程序的调试很有用,但通常还是需要自己来处理异常。因为,这样做一可以修正错误,二来可以防止程序的自动终止。
接下来,我们来看一下异常处理块的通用格式:
try {
// block of code to monitor for errors
}
catch ( ExceptionType1 exOb ) {
// exception handler for ExceptionType1
}
catch ( ExceptionType2 exOb ) {
// exception handler for ExceptionType2
}
// …
finally {
// block of code to be executed before try block ends
}
☆ try子句
用来监控可能产生错误的代码
☆ catch子句
用来捕获你指定的异常类型,并进行错误处理。它必须紧跟try块,并且可以定义多个catch块。
☆ finally子句
用来处理善后清理的工作,因为无论异常是否引发,它里面的代码都会执行,甚至你在try块中定义了return子句,它也一样会被执行。这是可选的。
5.2 Exception类前面我们讨论的所有异常都来自于Exception类。
在Java编程语言中,异常类可以大致分为三种。Java.lang.Throwable类充当所有对象的父类。它有Error和Exception两个基本子类,如下图所示:
Throwable类不能使用,而使用子类异常中的一个来描述任何特殊异常。
☆ Error表示恢复不是不可能但很困难的情况下的一种严重问题。比如说内存溢出。不可能指望程序能处理这样的情况。
☆ RuntimeException表示一种设计或实现问题。也就是说,它表示如果程序运行正常,从不会发生的情况。比如,如果数组索引扩展不超出数组界限,那么,ArrayIndexOutOfBoundsException异常从不会抛出。比如,这也适用于取消引用一个空值对象变量。因为一个正确设计和实现的程序从不出现这种异常,通常对它不做处理。这会导致一个运行时信息,应确保能采取措施更正问题,而不是将它藏到谁也不注意的地方。
☆ 其它异常表示一种运行时的困难,它通常由环境效果引起,可以进行处理。例子包括文件未找到或无效URL异常(用户打了一个错误的URL),如果用户误打了什么东西,两者都容易出现。这两者都可能因为用户错误而出现,这就鼓励程序员去处理它们。
在捕获异常的时候,一定要注意异常类型的匹配。只有catch块中定义的捕获异常的类型匹配引发的异常的类型或者其超类,才捕获并处理。所以,一定要注意你处理的异常类型的继承关系。另外,对于多个catch块来说,如果你定义的带捕获处理的异常有子类和相应的其超类,一定要注意放置的顺序,捕获超类的catch块一定要放置捕获其子类的catch块后面。
5.3 throws子句 如果一个方法可以导致一个异常但不处理它,它必须指定这种行为以使方法的调用者可以保护它们自己而不发生异常。你可以在方法声明中使用throws子句来实现这一目的。一个throws子句列举了一个方法可能引发的所有异常类型。这对于除了Error或者RuntimeException及它们的子类以外类型的所有异常是必要的。一个方法可以引发的所有其他类型的异常必须在throws子句中声明。
针对上面的情况,这里简单介绍一下异常的检查类型的概念。RuntimeException异常类及其子类就是非检查类型,Error类虽然不属于Exception类也是非检查类型的。对于非检查类型的异常,即使你在方法声明中没有指定它为可能引发的异常类型,它也会自动正确的被引发,系统保证它们的运行而不会出错。而除此以外的其他异常类型都是检查的,也就是如果你要引发它就必须在方法声明中指定,而且还必须使用catch块来捕获处理。
另外,对于throws子句还需要提一下的是其方法的重载问题。对于子类重载超类方法的这种覆盖重载override来说,要注意子类方法所引发的异常不能比超类的多,你引发的异常类型可以是超类引发异常类型的子类或者根本不引发任何异常,反正不可以超出其超类的范围。
5.4 引发和创建自定义异常
5.4.1 throw子句 前面我们都是获取的Java运行时系统引发的异常,然而,我们也可以在程序中用throw语句来引发明确的异常。Throw子句通用格式如下:
throw ThrowableInstance ;
ThrowableInstance一定是Throwable类类型或者其子类的一个对象,所以这里又涉及到了一个实例化异常类的问题。可以使用new来实例化异常类,需要注意的是,异常的有两个构造函数:一个没有参数,一个带有一个字符串参数。使用后者时,参数指定的是一个描述异常的字符串。如下:
throw new NullpointerException (“demo”) ;
5.4.2 创建自定义异常类 尽管Java内置异常可以处理大多数常见错误,你还是可以建立你自己的异常类型来满足你的特殊要求。建立的方法很简单,只要定义一个Exception 的一个子类就可以了。