参考博客:http://www.cnblogs.com/octobershiner/archive/2012/12/20/2827120.html
一、异常处理
异常处理通过五个关键字进行控制,他们是try、catch、throw、throws和finally。在程序中,在有可能出现不正常状况的地方,使用try关键字,用它吧这段代码包含起来。如果在try语句块中发生异常,这个异常就会被抛出。这个时候就可以使用catch语句来捕获异常,并在这块语句块中,对异常进行处理。还有一些不管发布发生异常,都需要执行的,就把它们放到finally语句块中。throw关键字用来手动引发一个异常。throws关键字用来定义任何被调用方法的异常。
基本使用
我们在使用java的一些文件或者数据库操作的时候已经接触过一些异常了,比如IOException、SQLException等,这些方法被声明可能会抛出某种异常,因此我们需要对其进行捕获处理。这就需要基本的try..catch语句了。下图就是我们经常写的一个基本结构。try语句块中写可能会抛出异常的代码,之后在catch语句块中进行捕获。我们看到catch的参数写的是一个Exception对象,这就意味着这个语句块可以捕获所有的检查类型的异常(虽然这并不是一种好的写法,稍后讨论),finally总是会保证在最后执行,一般我们在里面处理一些清理的工作,比如关闭文件流或者数据库,网络等操作。
语句块结构是灵活的,但是try是必须有的,catch和finally两者至少有一个,当然catche的数量可以有多个。有时候try语句块中可能抛出多种类型的异常,这个时候,我们可以写多个catch语句来捕获不同类型的异常,一个比较好的写法如下:
try{
// ..invoke some methods that may throw exceptions
}catch(ExceptionType1 e){
//...handle exception
}catch(ExceptionType2 e){
//...handle exception
}catch(Exception e){
//...handle exception
}finally{
//..do some cleaning :close the file db etc.
}
当异常不满足前两个type的时候,exception会将异常捕获。我们发现这个写法比较类似switch case的结构控制语句,但实际上,一旦某个catch得到匹配后,其他的就不会就匹配了,有点像加了break的case。有一点需要注意catch(Exception)一定要写在最后面,catch是顺序匹配的,后面匹配Exception的子类,编译器就会报错。
初次学习try..catch总会被其吸引,所以大量的使用这种结果,以达到某种“鲁棒性”。(这语句也是程序员表白的最爱)。但try语句实际上执行的时候会导致栈操作。即要保存整个方法的调用路径,这势必会使得程序变慢。fillInStackTrace()是Throwable的一个方法,用来执行栈的操作,他是线程同步的,本身也很耗时。这里问题在StackOverFlow上曾经有过一段非常经典的讨论
的确当我们在try中什么都不做,或者只执行一个类似加法的简单调用,那么其执行效率和goto这样的控制语句是几乎一样的。但是谁会写这样的代码呢?
总之不要总是试图通过try catch来控制程序的结构,无论从效率还是代码的可读性上都不好。
try catch好的一面
try catch虽然不推荐用于程序结构的控制,但是也具有重要的意义,其设计的一个好处就是,开发人员可以把一件事情当做事务来处理,事务也是数据库中重要的概念,举个例子,比如完成订单的这个事务,其中包括了一个动作序列,包括用户提交订单,商品出库,关联等。当这个序列中某一个动作执行失败的时候,数据统一恢复到一个正常的点,这样就不会出现,你付完了帐,商品却没有给你的情况。我们在try语句块中就像执行一个事务一样,当出现了异常,就会在catch中得到统一的处理,保证数据的完整无损。其实很多不好的代码也是因为没有好好利用catch语句的语言,导致很多异常就被淹没了。
异常处理原则
Java中异常提供了一种识别及响应错误情况的一致性机制,有效地异常处理能使程序更加健壮、易于调试。异常之所以是一种强大的调试手段,在于其回答了以下三个问题:
- 什么出了错?
- 在哪出的错?
- 为什么出错?
在有效使用异常的情况下,异常类型回答了“什么”被抛出,异常堆栈跟踪回答了“在哪“抛出,异常信息回答了“为什么“会抛出,如果你的异常没有回答以上全部问题,那么可能你没有很好地使用它们。有三个原则可以帮助你在调试过程中最大限度地使用好异常,这三个原则是:
- 具体明确
- 提早抛出
- 延迟捕获
(1)异常处理只能用于非正常情况
(2)为异常提供说明文档。通过javaDoc的@throws标签来描述异常的条件。尽可能的避免异常
(3)保持异常的原子性。异常的原子性是指当异常发生时后,各个对象的状态能够恢复到异常前的初始抓天,而不是停留在某个不合理的中间状态。
(4)避免过于庞大的try代码块
(5)在catch自居中指定具体的异常类型
(6)不要在catch代码块中忽略被捕获的异常。
二、Throwable类
类Throwable继承自Object,java所有的异常类都是继承Throwable。该类有两个子类,一个是Error,一个是Exception。Error类表示系统错误或编译期错误,如语法错误、函数书写错误等,一般不用。不过我们经常遇到且可以操作的异常是Exception类,这类异常是java标准函数库中抛出的基本异常,或者是用户自定义的异常,也可以是运行期发生的异常事件,如对象引用为null等。
public class Throwable extends Object implements Serializable
该类是Object的子类,实现Serializable接口。
1、构造函数表
Throwable() 构造一个将 null 作为其详细消息的新 throwable。 |
Throwable(String message)
构造带指定详细消息的新 throwable。 |
Throwable(String message, Throwable cause)
构造一个带指定详细消息和 cause 的新 throwable。 |
Throwable(Throwable cause)
构造一个带指定 cause 和 (cause==null ? null :cause.toString())(它通常包含类和 cause 的详细消息)的详细消息的新 throwable。 |
2、使用throw主动抛出异常
个人理解可以作为代替goto语句使用(部分代码放在try语句块中,部分代码放在catch代码块中和finally代码块中~如何操作可以多想想。重要的是编程思想嘛)
1 public class javaTest2 implements javaTest1 {
2
3 public static void main(String[] args) {
4
5 int a=1,b=2;
6 int max = 0;
7 try{
8 if(a>b){
9 max=a;
10 System.out.println("a>b!");
11 }else{
12 throw new Exception();
13 }
14 }catch(Exception e){
15 max=b;
16 System.out.println("a<b!");
17 }finally{
18 System.out.println(max);
19 }
20 }
21 }
运行结果:
a<b!
2
3、运行期异常(RuntimeException)
java程序中编写时RuntimeException是唯一可以省略的异常。所有运行期异常都继承于RuntimeException异常类,在编写程序时,不必考虑此类异常,所有函数默认自己可能抛出RuntimeException异常,系统会自动探测、捕获并处理运行期异常。由于编译器不强制捕获并处理运行期异常,所以此类异常会顺利的通过编译,可以想象程序运行时刻出现RuntimeException时,该异常会穿过层层方法,最后由系统捕获该异常,并输出相关信息。
4、Throwable类的方法
方法摘要 | |
---|---|
Throwable |
fillInStackTrace() 在异常堆栈跟踪中填充。 |
Throwable |
getCause()
返回此 throwable 的 cause;如果 cause 不存在或未知,则返回 null 。 |
String |
getLocalizedMessage()
创建此 throwable 的本地化描述。 |
String |
getMessage()
返回此 throwable 的详细消息字符串。 |
StackTraceElement[] |
getStackTrace()
提供编程访问由 printStackTrace()
输出的堆栈跟踪信息。 |
Throwable |
initCause(Throwable cause)
将此 throwable 的 cause 初始化为指定值。 |
void |
printStackTrace()
将此 throwable 及其追踪输出至标准错误流。 |
void |
printStackTrace(PrintStream s) 将此 throwable 及其追踪输出到指定的输出流。 |
void |
printStackTrace(PrintWriter s) 将此 throwable 及其追踪输出到指定的 PrintWriter。 |
void |
setStackTrace(StackTraceElement[] stackTrace)
设置将由 getStackTrace()
返回,并由 printStackTrace()
和相关方法输出的堆栈跟踪元素。 |
String |
toString()
返回此 throwable 的简短描述。 |
三、自定义异常
我们可以自己定义异常,以捕获处理某个具体的例子。创建自己的异常类,可以直接继承Exception或者RuntimeException。区别是前者是简称类型的,而后者为检查类型异常。Sun官方力挺传统的观点,他建议开发者都是用检查类型的异常,即你一定要去处理的异常。下面是定义的一个简单的异常类.
public class SimpleException extends Exception{
SimpleException(){}
SimpleException(String info){
super(info);
}
}
我们覆写了两个构造方法,这是有意义的。通过传递字符串参数,我们创建一个异常对象的时候,可以记录下详细的信息,这样这个异常被捕获的时候就会显示我们之前定义的详细信息。比如用下面的代码测试一下我们定义的异常类:
public class Test {
public void fun() throws SimpleException{
throw new SimpleException("throwing from fun");
}
public static void main(String[] args) {
Test t = new Test();
try{
t.fun();
}catch(SimpleException e){
e.printStackTrace();
}
}
}
运行就会得到下面的结果 printStackTrace是打印调用栈的方法,他有三个重载方法,默认的是将信息输出到System.err。这样我们就可以清晰的看到方法调用的过程,有点像操作系统中的中断,保护现场。
SimpleException: throwing from fun
at Test.fun(Test.java:4)
at Test.main(Test.java:9)
四、异常的几个问题
1、异常丢失
异常丢失是指函数抛出的异常没有被捕获,异常丢失是很重要的问题。而java的异常处理机制难以弥补这个缺陷。
个人觉得无非两个原因:捕获了异常没进行异常处理,finally子句提供了一种不管有无异常都需执行的一种机制。如果finally块中使用return、continue或break,则会把抛出的异常吃掉。
2、异常匹配
异常匹配讨论的是异常被抛出后,该如何选择处理函数的问题,其实,java提供的匹配机制很简单,异常被抛出后,系统会根据处理函数的顺序依次匹配,知道找到第一个可以处理该类异常的处理函数。系统会比较catch子句参数的异常类型,如果该类型与抛出的异常类型相符则处理异常,否则继续寻找。java异常处理机制对“异常类型相符”没有严格要求,认为子类的异常对象和父类的异常对象相符。如:
1 import java.io.IOException; 2 3 public class javaTest2 implements javaTest1 { 4 5 public static void main(String[] args) { 6 7 try{ 8 throw new IOException(); 9 }catch(IOException ex){ 10 System.out.println("IOException "); 11 }catch(Exception e){ 12 System.out.println("Exception"); 13 } 14 } 15 }
运行结果:
1 IOException
若把第一个
catch(IOException ex){
10 System.out.println("IOException ");注释了
运行结果为:
Exception
五、捕获异常(try...catch...)与抛出异常的区别(throws...)
捕获异常,可以由程序员自行对出现的异常进行处理
抛出异常,程序运行过程中产生的异常递交给虚拟机,由虚拟机进行处理,异常的处理程序员无法干预