异常、断言和日志
1.简介
在程序运行期间,用户可能会输入错误格式的数据造成程序的崩溃,这时Java使用一种异常处理的错误捕获机制来处理,这包括抛出异常和捕获异常。在测试期间需要进行大量的检测以验证程序操作的正确性,这时可以使用断言来有选择地启用检测。当程序出现错误时,需要用日志记录下出现的问题以备日后的分析。
2.处理错误
在程序运行时,如果由于出现错误而使得某些操作没有完成,程序应该:返回到一种安全状态,并能够让用户执行一些其他的命令;或允许用户保存所有操作的结果,并以妥善的方式终止程序。
下面是程序运行时可能出现的错误:
- 用户输入错误
- 设备错误
- 物理限制
- 代码错误
对于方法中的错误传统的做法是返回一个特殊的错误码,还有一种是表示错误情况的常用返回值是null引用。但并不是在任何情况下都能够返回一个错误码。
异常具有自己的语法和特定的继承结构。
在Java语言中,异常对象都是派生于Throwable类的一个实例,但是当Java中内置的异常类不能够满足需求,用户可以创建自己的异常类。Java异常层次结构:
Error类层次结构描述了Java运行时系统的内部错误和资源耗尽错误,它是程序无法处理的错误。如虚拟机错误、内存溢出错误、线程死锁等等。Exception层次结构是程序本身可以处理的异常,它有2个分支,其中由程序错误导致的异常属于RuntimeException(运行时异常),而程序本身没有问题,但由于像I/O、SQL等这类问题导致的异常属于编译时异常,编译时异常在写程序时必须处理。
派生于RuntimeException的异常有以下几种情况:错误的类型转换、数组访问越界、访问null指针;不是派生于RuntimeException的异常包括:试图在文件尾部后面读取数据、试图打开一个不存在的文件、试图根据给定的字符串查找Class对象,而这个字符串表示的类并不存在。应该通过检查数组下标是否越界来避免ArrayIndexOutOfBoundsException异常,在使用变量之前检测是否为null来杜绝NullPointerException的发生。
注:如果出现RuntimeException异常,那么就一定是你的问题。
Java语言规范将派生于Error类或RuntimeException类的所有异常称为非受查异常,这种异常要么不可控要么就应提前避免,所有其他的异常称为受查异常。
一个方法应该在其首部用throws声明所有可能抛出的异常。以下四种情况应该抛出异常:调用一个抛出受查异常的方法、程序运行过程中发现错误,并且利用throw语句抛出一个受查异常、程序出现错误、Java虚拟机和运行时库出现的内部错误。如果一个方法有可能抛出多个受查异常类型,那么就应该在方法的首部列出所有的异常类,每个异常类之间用逗号隔开。但是,不需要声明Java的内部错误,即从Error继承的错误,同样也不应该声明从RuntimeException继承的非受查异常。
注:如果父类方法没有抛出任何受查异常,子类也不能抛出任何受查异常。
写程序时当遇到任何标准异常类都不能解决时,可以定义自己的异常类,即定义一个派生于Exception的类,或者派生于Exception子类的类,自己定义的异常类应该包含两个构造器,一个是默认的构造器,另一个是可以输出详细描述信息的构造器,例父类Throwable的toString方法会打印这些详细信息。
3.捕获异常
如果在程序运行时发生了某个异常但没有在任何地方进行捕获,那么程序就会终止,并在控制台上打印异常信息,这包含异常的类型和堆栈的内容。对于图形界面程序(applet和应用程序),在捕获异常之后也会打印堆栈的信息,但程序将返回用户界面的处理循环中。throws用来声明将要抛出何种类型的异常,而throw用来抛出产生的异常。当一个方法可能会出现异常,但是它不想或没有能力处理这个异常,这时可以在方法声明处用throws来声明抛出异常。此时,谁调用了此方法谁就处理此异常。
throws后面跟单个或多个异常的类型,异常类型间用逗号来分隔。例,
public void test() throws ArithmeticException,ArrayIndexOutOfBoundsException {
}
throws关键字通常用于方法的声明中,如果是Error、RuntimeException或它们的子类,那么可以不使用throws关键字来声明要抛出的异常。编译仍能顺利通过。但在运行时会被系统抛出。
throw关键字通常用于方法体中,程序在执行到throw语句时,立即终止。它后面的语句都不执行,除非调用者捕获并处理了该异常。
注:throws后面跟的是异常的类名,throw后面跟的是异常的对象。throws指的是抛出一种异常的可能性,这种异常在程序中可能发生也可能不发生;throw抛出了异常,执行throw语句一定抛出了某种异常。
要想捕获异常,必须设置try/catch或try/catch-finally语句块。语句:
try {
代码块
产生异常的代码块
}catch(异常类型 ex ) {
对异常处理的代码块
}finally{
代码块
}
通常如果知道如何处理可能出现的异常就将它捕获,如果不知道如何处理就将它抛出。
不允许在子类的throws说明符中出现超过父类方法所列出的异常类范围。
finally语句块中的代码块无论是否发生异常代码都会执行,catch块中的return语句并不会阻止finally块的运行。
以下3种特殊情况下,finally块不会被执行:
- 在finally语句块中发生了异常
- 在前面的代码中用了System.exit()退出程序
- 程序所在的线程死亡
Java的异常处理是结构化,不会因为一个异常影响整个程序的执行。例,当try代码块中的语句发生了异常,程序就会调转到catch代码块中执行,执行完catch代码块中的程序代码后,继续执行catch代码块后的其他代码。不会执行try代码块中发生异常语句后面的代码。
在程序中想终止代码的执行可以使用java.lang.System下的exit(int status)方法,它可以终止当前运行的Java虚拟机,方法参数为非0整数。
Java中常见的RuntimeException异常,写异常时可通过try…catch语句捕获:
异常对象常用的3个方法:
- getMessage()函数:获取异常信息
- toString()函数:将异常对象转换为字符串,其中包括异常类型与错误信息
- printStackTrace()函数:指出异常的类型、性质、栈层次及出现在程序中的位置
在一个try语句块中可以捕获多个异常类型,即try块后可接0个或多个catch块,并对不同类型的异常做出不同的处理,即对每个异常类型使用单独的catch语句。如果没有catch块,则必须有一个finally块。
捕获多个异常时,异常变量隐含为final变量。建议在多个catch块语句后加一个catch(Exception)来处理可能会被遗漏的异常。可以在finally语句块中释放被占用的资源。
在try…catch语句中,多个catch的顺序一定要遵循“子类在上,父类在下”的规则。
注:当有多个catch语句时,多个catch语句中的异常类型不能相同。
在JDK7以后出现了在一个catch语句中处理多个异常的新特性,语法为:catch(异常1 | 异常2 | 异常3... 变量名)
。但是此时出现的异常只能是平级关系,不能出现子父类关系。
在使用异常时可以使用Java内置的异常类,也可以通过自定义异常来描述特定业务产生的异常类型。所谓自定义异常就是定义一个继承Throwable或它的子类的类,此时的自定义异常类要注意无参和带参的构造方法。
在使用异常时常常会遇到这样的问题:A方法会抛出一个异常且它调用了B方法,而B方法也会抛出一个异常,那么此时A方法是抛出一个原有的异常还是抛出一个新的异常?此时就出现了异常链即创建了新的异常但保留了原有异常的信息,将异常发生的原因一个传一个串起来,把下层的异常信息传递给上层,这样逐层抛出。其主要实现形式有两种:
使用异常的一些注意项:
- 异常处理不能代替简单的测试
- 不要过分地细化异常
- 利用异常层次结构
- 不要压制异常
- 早抛出,晚捕获
try…catch…finally…语句中有return的情况:
在没有特殊情况下,finally块语句是肯定会执行的。
public static int test(int a) {
try {
System.out.println(a/0);
}catch(Exception e) {
a=30;
return a; //此处在底层已经转换为return 30;所以在finally语句后并无变化
}finally {
a=40;
System.out.print(a); //结果40在前说明程序在return退出前执行了finally语句
}
return a; //前面return已退出,此处并没有执行
}
public static void main(String[] args) {
System.out.println(test(10)); //4030
}
public static int test(int b) {
try {
b += 10;
return b;
} catch (RuntimeException e) {
} catch (Exception e2) {
} finally {
b += 10;
return b;
}
}
public static void main(String[] args) {
int num = 10;
System.out.println(test(num)); //30
}
4.断言和日志暂无