简介:异常处理是java语言的重要特性之一,《Three Rules for effective Exception Handling》一文中是这么解释的:它主要帮助我们在debug的过程中解决下面的三个问题。

  什么出错了

  哪里出错了

  为什么出错

  java语言可以说是提供了过于完善的异常处理机制,以致于后来《Thinking in java》的作者Bruce Eckel都专门对他进行了论述。java中的异常机制包括Error和Exception两个部分。他们都继承自一个共同的基类Throwable。Error属于JVM运行中发生的一些错误,虽然并不属于开发人员的范畴,但是有些Error还是由代码引起的,比如StackOverflowError经常由递归操作引起,这种错误是告诉开发者,你一般无法挽救,只能靠JVM。而Exception假设程序员会去处理这些异常,比如数据库连接出了异常,那么我们可以处理这个异常,并且重新连接等。Exception分为两种,检查类型(checked)和未检查类型(unchecked)。检查类型的异常是说要程序员明确的去声明或者用try..catch语句来处理的异常,而非检查类型的异常则没有这些限制,比如我们常见的 NullPointerException 是非检查类型的,他继承自RuntimeException。java是目前主流编程语言中一个推崇使用检查类型异常的,至少sun是这样的。关于使用checked还是unchecked异常的论战一直很激烈。下面是一张java语言中异常的类关系图。

  基本使用

  我们在使用java的一些文件或者数据库操作的时候已经接触过一些异常了,比如IOException、SQLException等,这些方法被声明可能会抛出某种异常,因此我们需要对其进行捕获处理。这需要基本的try..catch语句了。下图是我们经常写的一个基本结构。try语句块中写可能会抛出异常的代码,之后在catch语句块中进行捕获。我们看到catch的参数写的是一个Exception对象,这意味着这个语句块可以捕获所有的检查类型的异常(虽然这并不是一种好的写法,稍后讨论),finally总是会保证在后执行,一般我们在里面处理一些清理的工作,比如关闭文件流或者数据库,网络等操作。

  当然上面的语句块结构是灵活的,但是try是必须有的,catch和finally两者至少有一个,当然catche的数量可以有多个。有时候try语句块中可能抛出多种类型的异常,这个时候,我们可以写多个catch语句来捕获不同类型的异常,一个比较好的写法如下:


1.try{ 
2.    // ..invoke some methods that may throw exceptions
3.}catch(ExceptionType1 e){ 
4.    //...handle exception
5.}catch(ExceptionType2 e){ 
6.    //...handle exception
7.}catch(Exception e){ 
8.    //...handle exception
9.}finally{ 
10.    //..do some cleaning :close the file db etc.
11.}
 


  当异常不满足前两个type的时候,exception会将异常捕获。我们发现这个写法比较类似switch case的结构控制语句,但实际上,一旦某个catch得到匹配后,其他的不会匹配了,有点像加了break的case。有一点需要注意catch(Exception)一定要写在后面,catch是顺序匹配的,后面匹配Exception的子类,编译器会报错。

  初次学习try..catch总会被其吸引,所以大量的使用这种结果,以达到某种“鲁棒性”。(这语句也是程序员表白的爱)。但try语句实际上执行的时候会导致栈操作。即要保存整个方法的调用路径,这势必会使得程序变慢。fillInStackTrace()是Throwable的一个方法,用来执行栈的操作,他是线程同步的,本身也很耗时。这里问题在StackOverFlow上曾经有过一段非常经典的讨论,原文。 的确当我们在try中什么都不做,或者只执行一个类似加法的简单调用,那么其执行效率和goto这样的控制语句是几乎一样的。但是谁会写这样的代码呢?

  总之不要总是试图通过try catch来控制程序的结构,无论从效率还是代码的可读性上都不好。