假设在一个Java程序运行期间出现了一个错误,我们先不讨论这个错误出现的原因;用户期望在出现错误的时候,程序能够采取一些理智的行为。比如:
返回到一种安全的状态,并能够让用户执行一些其他的命令。
允许用户保存所有操作的结果,并以适当的方式结束程序。
要做到这些并不是一件很容易的事情。其原因是监测(或者引发)错误条件的代码通常离那些能够让数据恢复到安全状态,或者能够保存用户的操作结果,并正常地退出程序的代码很远。异常处理的任务就是将控制权从错误产生的地方转移给能够处理这种情况的错误处理器。
在Java程序设计语言中,异常对象都是派生于Throwable类的一个实例。如果Java中内置的异常类不能够满足需求,用户可以创建自己的异常类。
Exception 和 Error 都是继承了 Throwable 类,在 Java 中只有 Throwable 类型的实例才可以被抛出(throw)或者捕获(catch),它是异常处理机制的基本组成类型。
Exception 和 Error 体现了 Java 平台设计者对不同异常情况的分类。
Exception 是程序正常运行中,可以预料的意外情况,可能并且应该被捕获,进行相应处理。这个层次结构又分为两个分支:一个分支派生于RuntimeException,另一个分支则包含其他异常。划分这两个分支的规则是:由程序错误导致的异常属于RuntimeException,而程序本身并没有问题的,但由于像I/O错误这种类型的异常属于其他异常。
派生于RuntimeException的异常包括以下几种情况:
1.错误的类型转换
2.数组访问越界
3.访问空指针
其他异常则包括以下几个方面:
1.试图在文件尾部后面读取数据
2.试图打开一个不存在的文件
3.试图根据给定的字符串查找Class对象,而这个字符串表示的类并不存在。
Error 类层次结构描述了Java运行时系统的内部错误和资源耗尽错误。这是在正常情况下,不大可能出现的情况,绝大部分的 Error 都会导致程序(比如 JVM 自身)处于非正常的、不可恢复状态。既然是非正常情况,所以不便于也不需要捕获,常见的比如 OutOfMemoryError 之类,都是 Error 的子类。如果出现了这种错误,除了通告给用户,并尽力使程序安全地终止之外,再也无能为力了。
Java语言规范将派生于Error类或RuntimeException类的所有异常称之为未检查异常,将其他异常称之为已检查异常。
对于已检查异常必须进行处理,即捕获或者抛出,对于未检查异常,可以不处理,即捕获、抛出或者不处理。
我们观察下面的代码,下面的代码反映了异常处理中哪些不当之处?
try{
//业务代码
//...
Thread.sleep(1000L);
}catch(Exception e){
//Ignore it
}
这段代码虽然很短,但是已经违反了异常处理的两个基本原则。
第一:尽量不要捕获类似 Exception 这样的通用异常,而是应该捕获特定异常。
第二,不要生吞(swallow)异常。这是异常处理中要特别注意的事情,因为很可能会导致非常难以诊断的诡异情况。
生吞异常,往往是基于假设这段代码可能不会发生,或者感觉忽略异常是无所谓的,但是千万不要在产品代码做这种假设!如果我们不把异常抛出来,或者也没有输出到日志(Logger)之类,程序可能在后续代码以不可控的方式结束。没人能够轻易判断究竟是哪里抛出了异常,以及是什么原因产生了异常。
我们再来观察第二段代码:
try{
//业务代码
//...
}catch(IOException e){
e.printStackTrace();
}
这段代码作为一段实验代码,它是没有任何问题的,但是在产品代码中,通常都不允许这样处理。你先思考一下这是为什么呢?
问题就在这里,在稍微复杂一点的生产系统中,标准出错(STERR)不是个合适的输出选项,因为你很难判断出到底输出到哪里去了。
尤其是对于分布式系统,如果发生异常,但是无法找到堆栈轨迹(stacktrace),这纯属是为诊断设置障碍。所以,最好使用产品日志,详细地输出到日志系统里。
4.1 系统自动抛出异常
当程序语句出现一些逻辑错误、或者类型转换错误的时候,系统会自动抛出异常
public static void main(String[] args){
int a=5;
int b=0;
System.out.println(a/b);
}
运行结果会自动抛出ArithmeticException异常(ArithmeticException是出现异常的运算条件时,抛出此异常。)
Exception in thread "main" java.lang.ArithmeticException: / by zero
at io.renren.modules.sys.controller.SysUserController.main(SysUserController.java:154)
4.2 throw
throw是语句抛出一个异常,一般是在代码的内部,当程序出现某种逻辑错误时,程序主动抛出某种特定类型的异常。
public static void main(String[] args){
String str="NBA";
if(str.equals("NBA")){
throw new NumberFormatException();
}else{
System.out.println(str);
}
}
运行结果,系统会抛出NumberFormatException异常。(当然,这里代码的问题不一定是NumberFormatException。这里只是随便拿一个异常举个例子)
Exception in thread "main" java.lang.NumberFormatException
at io.renren.modules.sys.controller.SysUserController.main(SysUserController.java:154)
4.3 throws
throws是方法可能会抛出一个异常(用在声明方法时,表示该方法可能要抛出异常)
public void function() throws Exception{...}
当某个方法可能会抛出某种异常时用throws 声明可能抛出的异常,然后交给上层调用它的方法程序处理
public static void testThrows() throws NumberFormatException {
String str = "NBA";
System.out.println(Integer.parseInt(str));
}
public static void main(String[] args) {
try {
testThrows();
} catch (NumberFormatException e) {
e.printStackTrace();
System.out.println("非数值类型不能强制类型转换");
}
}
运行结果
java.lang.NumberFormatException: For input string: "NBA"
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Integer.parseInt(Integer.java:580)
at java.lang.Integer.parseInt(Integer.java:615)
at io.renren.modules.sys.controller.SysUserController.testThrows(SysUserController.java:153)
at io.renren.modules.sys.controller.SysUserController.main(SysUserController.java:158)
非数值类型不能强制类型转换
4.4 throw与throws的比较
1、throws出现在方法函数头,而throw出现在函数体。
2、throws表示出现异常的一种可能性,并不一定会发生这些异常,throw则是抛出了异常,执行throw则一定抛出了某种异常对象。
3、两者都是消极处理异常的方式(这里的消极并不是说这种方式不好),只是抛出或者可能抛出异常,但不会由函数去处理异常,真正的处理异常由函数的上层调用处理。
当我们的服务出现反应变慢、吞吐量下降的时候,检查发生最频繁的 Exception 也是一种思路。关于诊断后台变慢的问题,我会在后面的 Java 性能基础模块中系统探讨。
try-catch 代码段会产生额外的性能开销,或者换个角度说,它往往会影响 JVM 对代码进行优化,所以建议仅捕获有必要的代码段,尽量不要一个大的 try 包住整段的代码;与此同时,利用异常控制代码流程,也不是一个好主意,远比我们通常意义上的条件语句(if/else、switch)要低效。
假如你开车上山,车坏了,你拿出工具箱修一修,修好继续上路(Exception被捕获,从异常中恢复,继续程序的运行),车坏了,你不知道怎么修,打电话告诉修车行,告诉你是什么问题,要车行过来修。(在当前的逻辑背景下,你不知道是怎么样的处理逻辑,把异常抛出去到更高的业务层来处理)。你打电话的时候,要尽量具体,不能只说我车动不了了。那修车行很难定位你的问题。(要捕获特定的异常,不能捕获类似Exception的通用异常)。还有一种情况是,你开车上山,山塌了,这你还能修吗?(Error:导致你的运行环境进入不正常的状态,很难恢复)