java编程思想--12通过异常处理错误

Java的基本理念是:结构不佳的程序不能运行。
异常处理是java中唯一的错误报告机制。改进的错误恢复机制是保证代码健壮性的最有力的的工具。
12.1概念
12.2基本异常
异常发生时:首先创建一个异常对象,终止原来的执行路径,弹出对异常对象的引用,此时异常处理机制接管程序,并寻找一个恰当的的地方来继续执行程序。
标准异常类都有两个构造器:1.默认构造器,2.接受将字符串作为参数,以便能把相关信息放入异常对象的构造器。
抛出异常 throw new Exception();
捕获异常 try{}catch(Exception e){};
12.4创建自定义异常
自定义异常类必须从已有的异常类继承,做好选择意思相近的异常类。
异常与记录日志:使用java.util.logging工具将将输出记录到日志中。
12.5异常说明
异常说明关键字throws,来告知可能抛出的异常。
12.6捕获所有异常
重抛异常会把捕获的异常抛给上一级的异常处理环境。
异常链?:捕获了一个异常后抛出另外一个异常,并希望把原始异常的的信息保存下来,这称为异常链。
现在所有的throwable子类在构造器都可以接受一个cause对象作为参数,这个cause表示原始异常
12.7Java标准异常
12.8使用finally进行清理
12.9异常的限制
派生类构造器不能捕获基类构造器抛出的异常。
覆盖方法的时候只能抛出基类异常说明里列出的异常。
一个出现在基类异常说明的中的异常不一定出现在派生类异常说明里。
12.10构造器
如果异常发生了,所有东西能被正常清理吗?
一些特殊的动作比如打开文件,这样的动作只有对象使用完毕调用特殊的清理方法之后才能得到清理,如果在构造器内抛出了异常,这些清理行为就不能正常工作了。
设计异常时:1.异常全部放在这一层处理2.处理一部分,然后向上抛出相同的异 常3.不做处理,直接向上层抛。
在构造器阶段会抛出遗产,并且要求清理的类,最安全的方式是使用嵌套的try语句。
12.11异常匹配
派生类可以匹配基类的处理程序。
12.12异常使用指南
1.恰当的级别处理问题。(在知道如何处理的情况下才捕获异常)
2.解决问题并重新调用产生异常的方法
3.进行少许修补绕过异常产生的地方继续执行
4.用别的数据来计算。以替换方法预计的值
5.把当前运行环境能做的事尽量做完,并把相同的异常重抛到更高层
6.把当前运行环境能做的事尽量做完,然后把不相同的异常重抛到更高层
7.终止程序
8.进行简化
9.让类库和程序更安全。

所谓的“描述正常执行过程中做了什么事”和“出了问题怎么办”代码分离。

举个简单的例子:
public class Box {
    public static void main(String[] args) {
        int i[] = new int[4];
        Box.change(i, 5, 1);
    }
    static void change(int[] i ,int index,int value){
        i[index] = value;
    }
}

这个传参如果超越数组边界的话怎么办,这是错误的啊,所以你用在方法中处理好,添加:
    static void change(int[] i ,int index,int value){
        if(index>i.length){
            System.out.println("IndexOutOfBound");
            return;
        }
        i[index] = value;
    }

这里的处理比较简单,如果这个程序里面有好多这样的问题,那你岂不是要在每一处都写上这个解决方法,这样就太麻烦了,异常就是这样用的。
  if(index>i.length){
            throw new IndexOutOfBoundsException();
        }

交给后面再去处理。

1)捕获异常
方法内部抛出异常,方法将在抛出异常的过程中结束,如果不希望方法就此结束,可以用try块来捕获异常。这样就不用每个方法都去加错误检查的代码,把他们一起捕获,对于相同的异常一次性解决。
try{
           
}catch(xx){
           
}catch(xx){

}
一旦有同类型的异常,便进入catch语句里面处理。


2)创建自定义异常
public class MyException {
    public void f(){
        System.out.println("f() throw simple exception!");
        try {
            throw new SimpleException();
        } catch (SimpleException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {
        MyException me = new MyException();
        me.f();
    }
}

这个例子有很多东西可以讲,首先,如果用了
throw new SimpleException();
那么,就要像上面一样加上try和catch。
或者,直接在方法中抛出:
    public void f() throws SimpleException{
        System.out.println("f() throw simple exception!");
            throw new SimpleException();
    }
但是这样直接抛出之后,
me.f();
就有问题了,以为异常抛出了,要么你在main方法中继续抛出:
    public static void main(String[] args) throws SimpleException {
        MyException me = new MyException();
        me.f();
    }
要么你在main方法中捕获异常进行处理:
public static void main(String[] args) {
        MyException me = new MyException();
        try {
            me.f();
        } catch (SimpleException e) {
            System.err.println("catch it!");
        }
    }
err输出为标准错误流,out输出为标准输出,err的话在控制台会以红色字体显示,比较醒目。
但你会发现输出结果是catch it! 然后才是f() throw simple exception。这个后面再提。
        try {
            me.f();
        } catch (SimpleException e) {
            e.printStackTrace(System.out);
        }
result:
son.SimpleException
    at son.MyException.f(MyException.java:8)
    at son.MyException.main(MyException.java:13)
printStackTrace(),栈跟踪,打印的是从方法调用处直到异常抛出处,默认无参数的话会输出到标准错误流。

3)记录日志
其实大型的网站,每天都有记录日志,哪里有错误再去日志里找,究竟发生了什么问题。 
public class LoggingException extends Exception{
    private static Logger logger = Logger.getLogger("LoggingException");
    public LoggingException(){
        StringWriter sw = new StringWriter();
        printStackTrace(new PrintWriter(sw));
        logger.severe(sw.toString());
    }
    public static void main(String[] args) {
        try {
            throw new LoggingException();
        } catch (LoggingException e) {
            System.err.println("catch "+e);
        }
    }
}
result:
九月 10, 2014 7:35:50 下午 son.LoggingException <init>
严重: son.LoggingException
    at son.LoggingException.main(LoggingException.java:16)

catch son.LoggingException
new PrintWriter(sw) 将输出抽取为String。
PrintWriter的话作为参数传进去,后面IO的时候再了解。
Logger,A Logger object is used to log messages for a specific system
or application component.
serve方法,Log a SEVERE message。

4)异常说明
Java鼓励人们把方法可能会抛出的异常告知使用此方法的客户端程序员。如果有源代码,那么客户端程序员可以通过查找throw语句获知抛出的异常。然而源代码一般不提供。

java提供了相应的语法,书上写得很有趣,说以礼貌的方式告诉客户端程序员某个方法可能会抛出的异常类型,他们可以进行相应的处理,这就是异常说明。
其实就是上面提到的不用try,catch而使用到的throws。

5)printStackTrace
我之前翻译成栈跟踪了,书上写的是栈轨迹,其实方法返回的是栈轨迹元素组成的数组,0为栈顶元素,先进后出,这是栈的特性。
public class TestElement {
    static void first(){
        try {
            throw new Exception();
        } catch (Exception e) {
            // 栈元素  拿到方法名
            for(StackTraceElement s : e.getStackTrace()){
                System.out.println(s.getMethodName());
            }
        }
    }
    static void second(){first();}
    static void third(){second();}
    public static void main(String[] args) {
        first();
        System.out.println("////////////////");
        second();
        System.out.println("////////////////");
        third();
    }
}
result:
first
main
////////////////
first
second
main
////////////////
first
second
third
main
整个栈元素的信息都可以逐一显示。

1)finally清理
对于一些代码,无论try有无抛出,都希望执行,为了这样可以在异常处理最后加上finally语句。印象最深的是与数据库打交道的时候,finally永远要记得回收Connection等等。
这样无论异常是否抛出,finally子句总能被执行。这样就不怕发生内存中的资源一直占用的情况,印象深刻的还有老师讲的,公司一个新来的忘记写finally语句,后面内存的东西越来越多,整个系统死掉了。

有一个特别的是,try中有return语句,还是会继续执行finally里的语句。

缺憾:异常丢失
class ImportantException extends Exception{
    public String toString(){
        return "import";
    }
}
class LightException extends Exception{
    public String toString(){
        return "light";
    }
}

public class LoseException {
    static void i() throws ImportantException{
        throw new ImportantException();
    }
    static void l() throws LightException{
        throw new LightException();
    }
    public static void main(String[] args) {
        try{
            try {
                i();
             }finally{
                l();}
        }catch (Exception e) {
            e.printStackTrace();
        }
    }
}

important的异常还是丢失了,虽然书上的作者早已希望未来的版本会修正这个问题,但是目前版本7还是没改善啊。
另外,finally子句只用return也会丢失异常。

2)构造器问题
这个问题比较复杂,看了好久,和之前处理的不一样。
书上的原话,构造器会把对象设置成安全的初始状态,但是会有别的动作,比如打开一个文件,这样的动作只有在对象使用完毕并且用户调用了特殊的清理方法之后才清理。
为什么说和以前不一样,以前确实是像作者所说,用finally最后处理就行了。但是,构造器在创建的时候就失败呢?

<span style="font-size:18px;">public class TestConstructor {
    private BufferedReader bf;
    public TestConstructor(String name) throws Exception{
        try {
            bf = new BufferedReader(new FileReader(name));
        } catch (FileNotFoundException e) {
            System.out.println("can not open!");
            //其实还没打开,不需要关闭
            throw e;
        } catch(Exception e){
            try{
                bf.close();
            }catch(IOException ie){
                System.out.println("close faild");
            }
        }finally{
            //不要在这里关闭
        }
    }
   
    public String getLine() {
        String s;
        try {
          s = bf.readLine();
        } catch(IOException e) {
            throw new RuntimeException("getLine failed!");
        }
        return s;
      }
   
    public void dispose(){
        try {
            bf.close();
            System.out.println("dispose successful");
        } catch (IOException e) {
            throw new RuntimeException("getLine failed!");
        }
    }
}</span>

io流,构造器传参之后能把FileReader和BufferedReader合在一起,看不懂的话可以提前翻一下io流的内容。FileNotFoundException,其实文件都找不到,我们是不用进行清理的,因为finally在每次构造器完成都执行一遍,不能把关闭的语句写在这里,为什么和以前把清理的代码写在finally中不同呢?
目的不同,在这里我们需要TestConstructor对象在整个生命周期都处于打开状态。而之前的那些我们希望的是快点用完快点关闭。

还有一个特别的地方,本来String是要初始化的,但是因为异常的抛出可以不用初始化。真正不用TestConstructor的时候,就调用dispose方法清除。

<span style="font-size:18px;">public class Clean {
    public static void main(String[] args) {
        try {
            TestConstructor tc = new TestConstructor("a.txt");
            try{
                String s ;
                int i ;
                while((s = tc.getLine())!=null){
                    //自己要进行的字符处理
                }
            }catch(Exception e){
                e.printStackTrace();
            }finally{
                tc.dispose();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally{
            System.out.println("failed constructed!");
        }
    }
}</span>

这种嵌套是要自己去写的,Eclipse不给提示,因为语句已经在一个try块里面了。仔细分析一下,这个嵌套写的很漂亮,因为构造失败的话,我们根本就不需要去关闭TestConstructor,相反,构造成功的话,我们最后记得要清理,这和上面红字的地方是对应的,TestConstructor是读取文件的,我们确实需要再整个生命周期里面打开,而Clean这个类是调用Testconstructor这个类的,当成功构造和使用完读取字符串之后,自然而然就用dispose关闭,只不过我以前写程序的时候没有注意到,finally的处理和try,catch的嵌套可以写得这么美,真让人拍案叫绝。

3)异常匹配
这个问题比较简单,异常抛出后,找到匹配的处理程序后,就认为异常将的到处理,不再继续寻找。
try {
} catch (FileNotFoundException e) {
   System.out.println("can not open!");
} catch(Exception e){
}
如果异常与第一个匹配,那就捕获,如果不是,就由Exception捕获,因为Exception会捕获Exception以及其他从它派生的异常。

其他可选方式:
异常处理一个原则是“只有你知道如何处理才去捕获异常”,前面一篇文章已经说过,异常其实是为了将错误发生的地点的代码和处理错误的代码分开,让你专心写前者的代码。

作者自己也曾经犯过错误:
    try{
    }catch(Exception e){}
称为:harmful if swallowed,吞食有害,异常有,因为catch是必须加上的,但是有没有进行处理,就这样被吞了。所以,要么写好错误处理,要么抛给高层。
 
4)历史与观点
历史就不说了,书上太长了。
说一下一个CLU设计师的观点——我们觉得强迫程序员在不知道该采取什么错事的时候提供处理程序是不现实的。但是我们也知道,放在那里不理也是不现实的,这是我自己的话,所以异常是个好东西。

总结:异常让我们可以集中精力解决问题,就是问题和业务逻辑的代码分开后再集中处理。
摘抄一下书上的异常使用指南:
把当前运行环境能做的事情尽量做完,然后把不同或者相同的异常抛向更高层。
让类库和程序更安全。
作为程序设计不可分割的一部分,要学会使用。

通过异常处理错误
基本异常
异常情形是指阻止当前方法或作用域继续执行的问题。
异常处理程序的任务是将程序从错误状态恢复,以使程序要么换一种方式运行,要么继续远行下去
可以创建一个代表错误信息的对象,并且将它从当前环境中“抛出”,这样错误信息就传播到了更大的环境中,将此称为抛出了一个异常。
if(t=null)
     throw new NullPointerException();
抛出了异常后,在当前环境中就不用再担心这个问题,它将会在别处得到处理
异常参数
用new在堆上创建异常对象
所有的标准异常类都有两个构造器:1.默认构造器 2.接受字符串作为参数,以便能把相关信息放入异常对象的构造器
  throw new NullPointerException("t=null");
捕获异常
try块
如果不希望方法内部抛出异常后就结束,可以在方法内设置一个特殊的块来捕获异常,它是跟在try关键字后面的普通程序块:
  try{
    //也许会出现错误的代码
  }
异常处理程序
抛出的异常必须在某处得到处理,这个地点就是异常处理程序,针对每个要捕获的程序,得准备相应的处理程序,通常紧跟在try块之后,已关键字catch表示:
  try{
    //也许会出现错误的代码
  }catch(Type1 id1) {
   //类型1的异常处理
  }catch(Type2 id2) {
   //类型2的异常处理
  }
异常处理程序必须紧跟在try块之后,只有匹配的catch子句才能得到执行
终止与恢复
Java支持终止模型:一旦异常被抛出,就表明错误已无法挽回,也不能回来继续执行
创建自定义异常
要自己定义异常类,必须从已有异常类继承
class SimpleException extends Exception {}
public class InheritingException {
  public void f() throws SimpleException {
    System.out.println("Throw SimpleException from f()");
    throw new SimpleException();
  }
  public static void main(String[] args) {
    InheritingException sed=new InheritingException();
    try {
        sed.f();
    } catch(SimpleException e){
        System.out.println("Caught it!");
    }
  }
}
异常说明
在程序库不将源代码一起发布的情况下,Java提供了相应的语法(并强制使用这个语法),告知客户端程序员某个方法可能会抛出异常类型,这就是异常说明,属于方法声明的一部分,紧跟在形式参数列表之后。
代码必须与异常说明保持一致
捕获所有异常
可以只写一个异常处理程序来捕获所有类型的异常,通过捕获异常类型的基类Exception就可以做到
catch(Exception e){
  System.out.println("Caught an exception");
}
使用finnally进行清理
对于一些代码,可能希望无论try块中的异常是否抛出,他们都能得到执行。为了达到这个效果,可以在异常处理程序后面加上finally子句。
class ThreeException extends Exception{}
public class FinallyWorks {
  static int count=0;
  public static void main(String[] args){
    while(true){
        try{
            //Post-increment is zero first time;
            if(count++==0)
                throw new ThreeException();
            System.out.println("NO exception");
        }catch(ThreeException e){
            System.out.println("ThreeException");
        }finally{
            System.out.println("In finally clause");
            if(count==2) break;//out of while
        }
     }
  }
}
可以得出,无论异常是否被抛出,finally子句总能被执行
finally用来做什么
当要把内存之外的资源恢复到它们初始的状态时,就要用到finally子句。
在return中使用finally
因为finally子句总是会执行的,所以在一个方法中,可以从多个点返回,并且可以保证重要的清理工作仍旧会执行。
public class MultipleReturns {
  public static void f(int i){
    System.out.println("Initialization that requires cleanup");
    try{
        System.out.println("Point 1");
        if(i==1) return;
        System.out.println("Point 2");
        if(i==2) return;
        System.out.println("Point 3");
        if(i==3) return;
        System.out.println("End");
        return;
    }finally{
        System.out.println("Performing cleanup");
    }
  }
  public static void main(String[] args){
    for(int i=1;i<=4;i++)
        f(i);
  }
}
异常的限制
当覆盖方法的时候,只能抛出在基类方法得异常说明里列出的那些异常,这意味着:当基类的代码应用到其派生类对象的时候,一样能够工作,异常也不例外。

1.概念
C语言以及其他早期语言常常具有多种错误处理模式,这些模式往往尽力在约定俗成的基础之上,而并不属于语言的一部分。通常会返回讴歌特殊值或者设置某个标志,并且假定接受者将对这个返回值或标志进行检查,以判定是否发生了错误。然而,随着时间的推移,人们发现,高傲的程序员们在使用程序库的时候更倾向于认为:“对,错误也许会发生,但那是别人造成的,不管我的事。正是由于程序员还仍然用这些方式拼凑系统,所以他们拒绝承认这样一个事实:对于构造大型、健壮、可维护的程序而言,这种错误处理模式已经成为了主要障碍。
解决的办法是,用墙纸规定的形式来消除错误处理过程中随心所欲的因素。这种做法由来已久,对异常处理的实现可追溯到20世纪60年代的操作系统,甚至于BASIC语言中的on error goto语句。而C++的异常处理机制基于Ada,Java中的异常处理则建立在C++的基础之上(尽管看上去使用更想Object Pascal).
”异常“这个词有”我对此感到意外“的意思。问题出现了,你也许不清楚该如何处理,但你的确知道不应该置之不理,你要停下来,看看是不是有别人或在别的地方,能够处理这个问题。只是在当前的环境中还没有足够的信息来解决这个问题,所以就把这个问题提交到一个更高级别的环境中,这里将作出正确的决定。使用异常所带来的另一个明显的好处是,它往往能够降低错误处理代码的复杂度。使用异常,那就不必在方法调用处进行检查,因为异常机制将保证能够捕获这个错误。并且,只需在一个地方处理错误,即所谓的异常处理程序中。这不仅节省代码,而且吧”描述在正常执行过程中做什么事“的代码和”出了问题怎么办“的代码分离。

2.基本异常
异常情形是指组织当前方法或作用域继续执行的问题。把异常情形与普通问题相区分很重要,所谓的普通问题是指,在当前环境下能够的到足够的信息,总能处理这个错误。而对于异常情形,就不能继续下去了,因为在当前环境下无法获得必要的信息来解决问题。你所能做的就是从当前环境跳出,并且把问题提交给上一级环境。这就是抛出异常时所发生的事情。
当异常抛出时,有几件事会随之发生。首先,同Java中其他对象的创建一样,将使用new在堆上创建异常对象。然后,当前的执行路径(它不能继续执行下去了)被终止,并且从当前环境中弹出对异常对象的引用。此时,异常处理机制接管程序,并开始寻找一个恰当的地方来继续执行程序。这个恰当的地方就是异常处理程序,它的任务就是将程序从错误状态中恢复,以使程序能要么换一种方式运行,要么继续运行下去。
异常使我们可以将每件事都当做一个事物来考虑,而异常可以看护这些事物的底线。我们还可以将异常看做是一种内建的恢复系统,因为(在细心使用的情况下)我们在程序中可以拥有各种不同的恢复点。如果程序的某部分失败了,异常将”恢复“到程序中某个已知的稳定点上。异常最重要的方面之一就是如果发生问题,他们将不允许程序沿着其正常的路径继续走下去。

2.1 异常参数
所有标准异常类都有两个构造器;一个是默认构造器;另一个是接受字符串作为参数,一边能把相关信息放入异常对象构造器。关键字throw将产生很多有趣的结果。在使用new创建了异常对象之后,此对象的引用将传给throw。能够抛出任何类型的Throwable对象,他是异常类型的跟类。通常,对于不同类型的错误,要抛出相应的异常。错误信息可以保存在异常对象内部或者用异常的名称来暗示。上一层环境通过这些信息来决定如何处理异常。

3.捕获异常
要明白异常是如何呗捕获的,必须首先理解监控区域的概念。他是一段可能产生异常的代码,并且后面跟着处理这些异常的代码。

3.1 try块
如果在方法内部抛出了异常(或者在方法内部调用的其他地方抛出了异常),这个方法将在抛出异常的过程中结束。要是不希望方法就此结束,可以在方法内设置一个特殊的块来捕获异常。因为这个块里”尝试“各种(可能产生异常的)方法调用,所以称为try块。

3.2 异常处理程序
当然,抛出的异常必须在某处得到处理。这个”地点“就是异常处理程序,而且针对每个要捕获的异常,得准备相应的处理程序。异常处理程序紧跟在try块之后,以关键字catch表示。每个catch子句看起来就像是接收一个且仅接受一个特殊类型的参数的方法。可以在处理程序内部使用标识符,这与方法参数的使用很相似。有时可能用不到标识符,因为异常的类型已经给了你足够的信息来对异常进行处理,但标识符并不可以省略。
异常处理程序必须紧跟在try块之后。当异常被抛出时,异常处理机制将负责搜索寻找参数与异常类型相匹配的第一个处理程序。然后进入catch子句执行,此时认为异常得到了处理。一旦catch子句结束,则处理程序的查找过程结束。注意,只有匹配的catch子句才能得到执行。注意在try块的内部,许多不同的方法可能会产生类型相同的异常,而你只需要提供一个针对此类型的异常处理程序。

3.3 终止与恢复
异常处理理论上有两种基本模型。Java支持终止模型(他是Java和C++所支持的模型)。在这种模型中,将假设错误非常关键,以至于程序无法返回到异常发生的地方继续执行。一旦异常被抛出,就表明错误已经无法挽回,也不能回来继续执行。
另一种称为恢复模型。意思是在异常处理程序的工作是修正错误,然后重新尝试调用出问题的方法,并认为第二次能成功。对于恢复模型,通常希望异常被处理之后能继续执行程序。如果想要用Java实现类似的恢复行为,那么在遇见错误时就不能抛出异常,而是调用方法来修正该错误。或者,把try块放在while循环里,这样就不断地进入try快,直到得到满意的结果。
长久以来以来,尽管从程序员们使用的操作系统支持恢复模型的异常处理,但他们最终还是转向使用类似”终止模型“的代码,并且忽略恢复行为。虽然恢复模型开始显得很吸引人,但不是很实用。其中的主要原因可能是它所导致的耦合,恢复性的处理程序需要了解异常抛出地点,这是比要包含依赖于抛出未知的非通用性代码。这增加了代码编写和维护的困难,对于异常可能会从愈多地方抛出的大型程序来说,更是如此。

4.创建自定义异常
不必拘泥于Java中已有的异常类型。Java提供的异常体系不可能预见所有的希望加以报告的错误,所以可以自己定义异常来表示程序中可能会遇到的特定问题。
要自己定义异常类,必须从已有的异常类继承,最好是选择意思相近的异常类继承(不过这样的异常并不容易找)。建立新的异常类型最简单的方法就是让编译器为你产生默认构造器,所以这几乎不用写多少代码。
4.1 异常与记录日志
可以使用java.util.logging工具将输出记录到日志中。这是一种很好的做法。

5.异常说明
Java鼓励人们把方法可能会抛出的异常告知使用此方法的客户端程序员。Java提供了相应的语法(并强制使用这个语法),使你能以礼貌的方式告知客户端程序员某个方法可能会抛出的异常类型,然后客户端程序员就可以进行相应的处理。这就是异常说明,它属于方法声明的一部分,紧跟在形式参数列表之后。代码必须与异常说明一致。如果方法里的代码产生了异常却没有处理,编译器会发现这个问题并提醒你:要么处理异常,要么就在异常说明中表明此方法产生异常。通过这种自顶向下强制执行的异常说明机制,Java在编译时就可以保证一定水平的异常正确性。
不过还是有个能左必“的地方:可以声明方法抛出异常,实际上却不抛出。编译器相信了这个声明,并强制此方法的用户像真的抛出异常那样使用这个方法。这样做的好处是,为异常先占个位子,以后就可以抛出这种异常而不用修改已有的代码。在定义抽象基类和接口时,这种能力很重要,这样派生类或接口实现就能够抛出这些预先声明的异常。这种在编译时被强制检查的异常称为被检查的异常。

6.捕获所有异常
可以执行一个异常处理程序来捕获所有类型的异常。通过捕获异常类型的基类Exception,你就可以做到这一点(事实上还有其他的基类,但Exceptiong是同变成活动相关的基类)。这种捕获所有异常的代码,最好把它放在处理程序列表的末尾,以防止它抢在其他处理程序之前先把异常捕获了。

6.1 栈轨迹
printStackTrace()方法所提供的信息可以通过getStackTrace()方法来直接访问,这个方法将返回一个由栈轨迹中的元素所构成的数组,其中每一个元素都表示栈中的一桢。元素0是栈顶元素,并且是调用序列中最后一个方法调用(这个Throwable被创建和抛出之处)。数组中的最后一个元素和栈底是调用序列中的第一个方法调用。

6.2 重新抛出异常
有时希望把刚捕获的异常重新抛出,尤其是在使用Exception捕获所有异常的时候。既然已经得到了对当前异常对象的引用,可以直接把它重新抛出。重新抛出异常会把异常抛给上一级环境中的异常处理程序,同一个try块后续catch子句将被忽略此外,异常对象的所有信息都得以保持,所以高一级环境中捕获此异常的处理程序可以从这个异常对象中得到所有信息。
如果只是把当前对象重新抛出,那么printStackTrace()方法将显示原来异常抛出点的调用信息,而并非重新抛出点的信息。想要更新这个信息,可以调用fillInStackTrack()方法,这将返回一个Throwable对象,他是通过把当前调用栈信息填入原来那个异常对象而建立的。
有可能在捕获异常之后抛出另一种异常。这么做的话,得到的效果类似于使用fillInStackTrace(),有关原来异常发生点的信息会丢失,剩下的是新的抛出点有关的信息。

6.3 异常链
常常会想要在捕获一个异常后抛出另一个异常,并且希望把原始异常的信息保存下来,这被称为异常链。在JDK 1.4以前,程序员必须自己编写代码来保存原始异常的信息。现在所有Throwable的子类在构造器中都可以接受一个cause对象作为参数。这个cause就用来表示原始异常,这样通过把原始异常传递给新的异常,就使得即使哎当前位置创建并抛出新的异常,也能铜鼓这个异常链追踪到异常最初发生的位置。在Throwable的子类中,只有三种基本的异常类提供了带cause参数的构造器。它们是Error,Exception和RuntimeException。如果要把其他类型的异常链接起来,应该使用initCause()方法而不是构造器。

7.Java标准异常
Throwable这个Java类被用来表示任何可以作为异常被抛出的类。Throwable对象可分为两种类型(指从Throwable继承而得到的类型):Error用来表示编译时和系统错误(除特殊情况外,一般不用你关心);Exception是可以被抛出的基本类型,在Java类库、用户方法以及运行时故障中都可以抛出Exception型异常。

7.1 特例:RuntimeException
RuntimeException是Java的标准运行时检测的一部分。它们会被Java虚拟机抛出,所以不必在异常说明中把它们列出来。这构成了一组具有相同特征和行为的异常类型。并且,也不再需要在异常说明中声明方法将抛出RuntimeException类型异常,它们被称为“不受检查异常”。这种异常属于错误,将被自动捕获。

8.使用finally进行清理
对于一些代码,可能会希望无论try块中的异常是否抛出,它们都能得到执行。这通常适用于内存回收之外的情况(因为回收由垃圾回收器完成)。为了达到这个效果,可以在异常处理程序后面加上finally子句。
8.1 finally用来做什么
当要把除内存之外的资源恢复到他们的初始状态时,就要用到finally子句。这种需要清理的资源包括:已经打开的文件或网络连接,在屏幕上画的图形,甚至可以是外部世界的某个开关。
8.2 在return中使用finally
因为finally子句总是会执行的,所以在一个方法中,可以从多个点返回,并且可以保证重要的清理工作仍旧会执行。
8.3 缺憾:异常丢失
遗憾的是,Java的异常实现也有瑕疵。异常作为程序出错的标志,绝不应该被忽略,但它还是有可能被轻易地忽略。用某些特殊的方式使用finally子句,就会发生这种情况。

9.异常的限制
当覆盖方法的时候,只能抛出在基类方法的异常说明里列出的哪些异常。这个限制很有用,因为这意味着,当基类使用的代码应用到派生类对象的时候,一样能够工作(当然,这是面向对象的基本概念),异常也不例外。

10.构造器
有一点很重要,即你要时刻询问自己“如果异常发生了,所有东西能被正确的清理吗“?尽管大多数情况下是非常安全的,但涉及构造器时,问题就出现了。构造器会啊对象设置成安全的初始状态,但还会有别的动作,比如打开一个文件,这样的动作只有在对象使用完毕并且用户调用了特殊的清理方法之后才能得以清理。如果在构造器内抛出了异常,这清理行为也许就不能正常工作。这意味着在编写构造器是要格外的小心。
读者也许会认为使用finally就可以解决问题。但问题并非如此简单,因为finally会每次都执行清理代码。如果构造器在执行过程中半途而废,也许该对象的某些部分还没有被成功创建,而这些部分在finally子句中确是要被清理的。
对于在构造阶段可能会抛出异常,并且要求清理的类,最安全的使用方式是使用嵌套的try。这种通用的清理习惯在构造器不抛出任何异常时也应该运用,其基本规则是:在创建需要清理的对象之后,立即进入一个try-finally语句块。

11.异常匹配
抛出异常的时候,异常处理系统会按照代码的书写顺序找出”最近“的吹程序。找到匹配的处理程序之后,他就认为异常将得到处理,然后就不再继续查找。
查找的时候不要求抛出的异常同处理程序所声明的异常完全匹配。派生类的对象也可以匹配其基类的处理程序。如果把捕获积累的catch子句放在最前面,就会把派生类的异常全给”屏蔽“掉。

12.其他可选方式
这个地方很深奥,是作者的深层次的探讨和研究。以后再学吧。

13.异常使用指南
应该在下列情况下使用异常:
1)在恰当的级别处理问题(在知道该如何处理的情况下才捕获异常)。
2)解决问题并且重新调用产生异常的方法。
3)进行少许修补,然后绕过异常发生的地方继续执行
4)用别的数据进行计算,以代替方法预计会返回的值。
5)把当前运行环境下能做的事情尽量做完,然后把相同的异常重抛到更高层。
6)把当前运行环境下能做的事情尽量做完,然后把不同的异常抛到更高层。
7)终止程序。
8)进行简化。
9)让类库和程序更安全。

一、概念

二、基本异常

当抛出异常之后,有几件事情会随之发生:
首先,同java中其他对象的创建一样,将使用new在堆上穿件异常对象。然后当前的执行路径(它不能继续下去了)被终止,并且从当前环境中弹出对异常对象的引用。此时,异常处理机制接管程序,并开始寻找一个恰当的地方来继续执行程序。这个恰当的地方就是异常处理程序。

1、异常参数

1)所有标准异常类都有两个构造器:一个是默认构造器,一个是接受字符串作为参数,以便能够相关信息放入异常对象的构造器
2)Throwable对象,它是异常类型的根类。

三、捕获异常

监控区域

1、try块
异常处理程序必须紧跟在try块之后。当异常被抛出时候,异常处理机制将负责搜寻参数与异常类型相匹配的第一个处理程序。然后进入catch子句执行,此时认为异常得到了处理。一旦catch子句结束,则处理程序的查找过程结束。

2、终止与恢复

异常处理理论上有两种基本类型。java支持终止模型(它是java和c++所支持的模型)。

四、创建自定义的异常

要子句定义异常类,必须从已有的异常类继承,最好是选择意思相近的异常类继承。

printStackTrace()方法:从方法调用处直到异常抛出处的方法调用序列。

1、异常与记录日志





静态的Logger.getLogger()方法创建一个String参数相关联的Logger对象(通常与错误相关的包名和类名),这个Logger对象会将其输出发送到System.err。向Logger写入的最简单方法就是直接调用与日志记录消息级别相关联的方法,这里使用的是severe().

五、异常说明

可以声明方法将抛出异常,实际上却不抛出
被检查的异常:在编译时候被强制检查的异常

六、捕获所有异常

1、栈轨迹
printStackTrace()方法所提供的信息可以通过getStackTrace()方法来直接访问,这个方法将返回一个由栈轨迹中的元素所构成的数组,其中每一个元素都表示栈中的一帧。
2、重新抛出异常

如果只是把当前异常对象重新抛出,那么printStackTrace()方法显式的将是原来异常抛出点的调用栈信息,而并非重新抛出点的信息。要想更新这个信息,可以调用fillInStackTrace()方法,这将返回一个Throwable对象,它是通过把当前调用栈信息填入原来那个异常对象而建立的:





有可能在捕获异常之后抛出另一个异常。那么做的话,得到的效果类似于使用fillInStackTrace(),有关原来异常发生点的信息会丢失,剩下的是与新的抛出点有关的信息。

永远不必为清理前一个一场对象而担心,或者说为异常对象的清理而担心。它们都是用new在堆上创建的对象,所以垃圾回收器会自动把它们清理掉。

3、异常链

在Throwable的子类中,只有三种基本的异常提供了带cause参数的构造器,它们是Error(用于java虚拟机报告系统错误)、Exception以及RuntimeException。如果要把其他类型的异常链接起来,应该使用initCause()方法而不是构造器。

七、java标准异常

1、特例:RuntimeException

RuntimeException(或者任何从它继承的异常)是一个特例。对于这种异常类型,编译器不需要异常说明,并输出被报告给了System.err.

如果RuntimeException没有被捕获而直达main(),那么在程序退出前将调用异常的printStackTrace()方法。

只能在代码中忽略RuntimeException(及其子类)的异常,其他类型的异常的处理都是由编译器强制实施的。究其原因,RuntimeException代表的是编程错误:
1)无法预料的错误。比如从你控制范围之外传递进来的null引用
2)代码中进行检查的错误

八、使用finally进行清理

1、finally用来做什么

当要把除了内存之外的资源恢复到它们的初始状态的时候,就要用到finally子句。这种需要清理的资源包括:已经打开的文件或者网络连接,在屏幕上画的图形,甚至可以是外部世界的某个开关。

2、在return中使用finally
3、缺憾:异常丢失
九、异常的限制
当覆盖方法的时候,只能抛出在基类方法的异常说明里列出的那些异常。这个限制很有用,因为这意味着,当基类使用的代码应用到其他派生类对象的时候,一样能够工作,异常也不例外:


1)异常限制对构造器不起作用

你会发现StormyInning的构造器可以抛出任何异常,而不必理会基类构造器所抛出的异常。然后,因为基类构造器必须以这样或者那样的方式被调用,派生类构造器的异常说明必须包含基类构造器的异常说明。

2)StormyInning.walk()不能通过编译的原因:它抛出了异常,而Inning.walk()并没有声明此异常。

3)覆盖后的event()方法表明,派生类方法可以不抛出异常,即使它是基类所定义的异常。

4)在main方法中,如果处理的刚好是StormyInning对象的话,编译器只会强制要求你捕获这个类所抛出的异常。但是如果将它向上转型为基类型,那么编译器就会(正确的)要求你捕获基类的异常。

5)一个出现在基类方法中的异常说明中的异常,不一定会出现在派生类方法的异常说明里。这点同继承的规则明显不同,在继承中,基类方法必须出现在派生类里。换句话说,在继承和覆盖的过程中,某个特定的方法的“异常说明的接口”不是变大而是变小了----这恰好和类接口在继承时候的情形相反。

十、构造器

基本规则:在创建需要清理的对象之后,立即进入一个try-finally语句块。

对于在构造阶段可能会抛出异常,并且要求清理的类,最安全的使用方式就是使用嵌套的try子句。

十一、异常匹配

查找的时候并不要求抛出的异常同处理程序所声明的异常完全匹配,派生类的对象也没有匹配其基类的处理程序
如果把捕获基类的catch子句放在最前面,以此想把派生类的异常全给“屏蔽”,就像这样:

这样编译器就会发现Sneeze的catch子句永远也得不到执行,因此它会向你报告错误。

Java异常处理总结
 
        异常处理是程序设计中一个非常重要的方面,也是程序设计的一大难点,从C开始,你也许已经知道如何用if...else...来控制异常了,也许是自发的,然而这种控制异常痛苦,同一个异常或者错误如果多个地方出现,那么你每个地方都要做相同处理,感觉相当的麻烦!
        Java语言在设计的当初就考虑到这些问题,提出异常处理的框架的方案,所有的异常都可以用一个类型来表示,不同类型的异常对应不同的子类异常(这里的异常包括错误概念),定义异常处理的规范,在1.4版本以后增加了异常链机制,从而便于跟踪异常!这是Java语言设计者的高明之处,也是Java语言中的一个难点,下面是我对Java异常知识的一个总结,也算是资源回收一下。
一、Java异常的基础知识
 
        异常是程序中的一些错误,但并不是所有的错误都是异常,并且错误有时候是可以避免的。比如说,你的代码少了一个分号,那么运行出来结果是提示是错误java.lang.Error;如果你用System.out.println(11/0),那么你是因为你用0做了除数,会抛出java.lang.ArithmeticException的异常。
        有些异常需要做处理,有些则不需要捕获处理,后面会详细讲到。
        天有不测风云,人有旦夕祸福,Java的程序代码也如此。在编程过程中,首先应当尽可能去避免错误和异常发生,对于不可避免、不可预测的情况则在考虑异常发生时如何处理。
        Java中的异常用对象来表示。Java对异常的处理是按异常分类处理的,不同异常有不同的分类,每种异常都对应一个类型(class),每个异常都对应一个异常(类的)对象。
        异常类从哪里来?有两个来源,一是Java语言本身定义的一些基本异常类型,二是用户通过继承Exception类或者其子类自己定义的异常。Exception 类及其子类是 Throwable 的一种形式,它指出了合理的应用程序想要捕获的条件。
        异常的对象从哪里来呢?有两个来源,一是Java运行时环境自动抛出系统生成的异常,而不管你是否愿意捕获和处理,它总要被抛出!比如除数为0的异常。二是程序员自己抛出的异常,这个异常可以是程序员自己定义的,也可以是Java语言中定义的,用throw 关键字抛出异常,这种异常常用来向调用者汇报异常的一些信息。
        异常是针对方法来说的,抛出、声明抛出、捕获和处理异常都是在方法中进行的。
        Java异常处理通过5个关键字try、catch、throw、throws、finally进行管理。基本过程是用try语句块包住要监视的语句,如果在try语句块内出现异常,则异常会被抛出,你的代码在catch语句块中可以捕获到这个异常并做处理;还有以部分系统生成的异常在Java运行时自动抛出。你也可以通过throws关键字在方法上声明该方法要抛出异常,然后在方法内部通过throw抛出异常对象。finally语句块会在方法执行return之前执行,一般结构如下: try{  程序代码 }catch(异常类型1 异常的变量名1){  程序代码 }catch(异常类型2 异常的变量名2){  程序代码 }finally{  程序代码 }
        catch语句可以有多个,用来匹配多个异常,匹配上多个中一个后,执行catch语句块时候仅仅执行匹配上的异常。catch的类型是Java语言中定义的或者程序员自己定义的,表示代码抛出异常的类型,异常的变量名表示抛出异常的对象的引用,如果catch捕获并匹配上了该异常,那么就可以直接用这个异常变量名,此时该异常变量名指向所匹配的异常,并且在catch代码块中可以直接引用。这一点非常非常的特殊和重要!
        Java异常处理的目的是提高程序的健壮性,你可以在catch和finally代码块中给程序一个修正机会,使得程序不因异常而终止或者流程发生以外的改变。同时,通过获取Java异常信息,也为程序的开发维护提供了方便,一般通过异常信息就很快就能找到出现异常的问题(代码)所在。
        Java异常处理是Java语言的一大特色,也是个难点,掌握异常处理可以让写的代码更健壮和易于维护。
 
二、Java异常类类图
 
下面是这几个类的层次图:java.lang.Object  java.lang.Throwable      java.lang.Exception       java.lang.RuntimeException   java.lang.Error       java.lang.ThreadDeath
 
下面四个类的介绍来自java api 文档。
 
1、Throwable        Throwable 类是 Java 语言中所有错误或异常的超类。只有当对象是此类(或其子类之一)的实例时,才能通过 Java 虚拟机或者 Java throw 语句抛出。类似地,只有此类或其子类之一才可以是 catch 子句中的参数类型。
两个子类的实例,Error 和 Exception,通常用于指示发生了异常情况。通常,这些实例是在异常情况的上下文中新近创建的,因此包含了相关的信息(比如堆栈跟踪数据)。
 
2、Exception        Exception 类及其子类是 Throwable 的一种形式,它指出了合理的应用程序想要捕获的条件,表示程序本身可以处理的异常。
 
3、Error        Error 是 Throwable 的子类,表示仅靠程序本身无法恢复的严重错误,用于指示合理的应用程序不应该试图捕获的严重问题。
在执行该方法期间,无需在方法中通过throws声明可能抛出但没有捕获的 Error 的任何子类,因为Java编译器不去检查它,也就是说,当程序中可能出现这类异常时,即使没有用try...catch语句捕获它,也没有用throws字句声明抛出它,还是会编译通过。
 
4、RuntimeException        RuntimeException 是那些可能在 Java 虚拟机正常运行期间抛出的异常的超类。Java编译器不去检查它,也就是说,当程序中可能出现这类异常时,即使没有用try...catch语句捕获它,也没有用throws字句声明抛出它,还是会编译通过,这种异常可以通过改进代码实现来避免。
 
5、ThreadDeath        调用 Thread 类中带有零参数的 stop 方法时,受害线程将抛出一个 ThreadDeath 实例。
        仅当应用程序在被异步终止后必须清除时才应该捕获这个类的实例。如果 ThreadDeath 被一个方法捕获,那么将它重新抛出非常重要,因为这样才能让该线程真正终止。
如果没有捕获 ThreadDeath,则顶级错误处理程序不会输出消息。
        虽然 ThreadDeath 类是“正常出现”的,但它只能是 Error 的子类而不是 Exception 的子类,因为许多应用程序捕获所有出现的 Exception,然后又将其放弃。
 
        以上是对有关异常API的一个简单介绍,用法都很简单,关键在于理解异常处理的原理,具体用法参看Java API文档。
 
三、Java异常处理机制
        对于可能出现异常的代码,有两种处理办法:        第一、在方法中用try...catch语句捕获并处理异常,catach语句可以有多个,用来匹配多个异常。例如:public void p(int x){ try{  ... }catch(Exception e){  ... }finally{  ... }}
 
第二、对于处理不了的异常或者要转型的异常,在方法的声明处通过throws语句抛出异常。例如:
public void test1() throws MyException{ ... if(....){  throw new MyException(); }} 
        如果每个方法都是简单的抛出异常,那么在方法调用方法的多层嵌套调用中,Java虚拟机会从出现异常的方法代码块中往回找,直到找到处理该异常的代码块为止。然后将异常交给相应的catch语句处理。如果Java虚拟机追溯到方法调用栈最底部main()方法时,如果仍然没有找到处理异常的代码块,将按照下面的步骤处理:        第一、调用异常的对象的printStackTrace()方法,打印方法调用栈的异常信息。        第二、如果出现异常的线程为主线程,则整个程序运行终止;如果非主线程,则终止该线程,其他线程继续运行。
        通过分析思考可以看出,越早处理异常消耗的资源和时间越小,产生影响的范围也越小。因此,不要把自己能处理的异常也抛给调用者。
        还有一点,不可忽视:finally语句在任何情况下都必须执行的代码,这样可以保证一些在任何情况下都必须执行代码的可靠性。比如,在数据库查询异常的时候,应该释放JDBC连接等等。finally语句先于return语句执行,而不论其先后位置,也不管是否try块出现异常。finally语句唯一不被执行的情况是方法执行了System.exit()方法。System.exit()的作用是终止当前正在运行的 Java 虚拟机。finally语句块中不能通过给变量赋新值来改变return的返回值,也建议不要在finally块中使用return语句,没有意义还容易导致错误。
 
        最后还应该注意一下异常处理的语法规则:        第一、try语句不能单独存在,可以和catch、finally组成 try...catch...finally、try...catch、try...finally三种结构,catch语句可以有一个或多个,finally语句最多一个,try、catch、finally这三个关键字均不能单独使用。
        第二、try、catch、finally三个代码块中变量的作用域分别独立而不能相互访问。如果要在三个块中都可以访问,则需要将变量定义到这些块的外面。
        第三、多个catch块时候,Java虚拟机会匹配其中一个异常类或其子类,就执行这个catch块,而不会再执行别的catch块。
        第四、throw语句后不允许有紧跟其他语句,因为这些没有机会执行。
        第五、如果一个方法调用了另外一个声明抛出异常的方法,那么这个方法要么处理异常,要么声明抛出。
 
        那怎么判断一个方法可能会出现异常呢?一般来说,方法声明的时候用了throws语句,方法中有throw语句,方法调用的方法声明有throws关键字。
 
        throw和throws关键字的区别        throw用来抛出一个异常,在方法体内。语法格式为:throw 异常对象。        throws用来声明方法可能会抛出什么异常,在方法名后,语法格式为:throws 异常类型1,异常类型2...异常类型n。
 
四、如何定义和使用异常类
 
1、使用已有的异常类,假如为IOException、SQLException。
 try{  程序代码 }catch(IOException ioe){  程序代码 }catch(SQLException sqle){  程序代码 }finally{  程序代码 }
 
2、自定义异常类创建Exception或者RuntimeException的子类即可得到一个自定义的异常类。例如: public class MyException extends Exception{  public MyException(){}  public MyException(String smg){   super(smg);  } }
 
3、使用自定义的异常用throws声明方法可能抛出自定义的异常,并用throw语句在适当的地方抛出自定义的异常。例如:
在某种条件抛出异常public void test1() throws MyException{ ... if(....){  throw new MyException(); }}
 
将异常转型(也叫转译),使得异常更易读易于理解public void test2() throws MyException{ ... try{  ... }catch(SQLException e){  ...  throw new MyException(); }}
 
还有一个代码,很有意思:public void test2() throws MyException{ ... try {  ... } catch (MyException e) {  throw e; } }
这段代码实际上捕获了异常,然后又和盘托出,没有一点意义,如果这样还有什么好处理的,不处理就行了,直接在方法前用throws声明抛出不就得了。异常的捕获就要做一些有意义的处理。
 
五、运行时异常和受检查异常
Exception类可以分为两种:运行时异常和受检查异常。1、运行时异常RuntimeException类及其子类都被称为运行时异常,这种异常的特点是Java编译器不去检查它,也就是说,当程序中可能出现这类异常时,即使没有用try...catch语句捕获它,也没有用throws字句声明抛出它,还是会编译通过。例如,当除数为零时,就会抛出java.lang.ArithmeticException异常。
2、受检查异常除了RuntimeException类及其子类外,其他的Exception类及其子类都属于受检查异常,这种异常的特点是要么用try...catch捕获处理,要么用throws语句声明抛出,否则编译不会通过。
3、两者的区别运行时异常表示无法让程序恢复运行的异常,导致这种异常的原因通常是由于执行了错误的操作。一旦出现错误,建议让程序终止。受检查异常表示程序可以处理的异常。如果抛出异常的方法本身不处理或者不能处理它,那么方法的调用者就必须去处理该异常,否则调用会出错,连编译也无法通过。当然,这两种异常都是可以通过程序来捕获并处理的,比如除数为零的运行时异常:
public class HelloWorld { public static void main(String[] args) {  System.out.println("Hello World!!!");   try{   System.out.println(1/0);  }catch(ArithmeticException e){   System.out.println("除数为0!");  }  System.out.println("除数为零后程序没有终止啊,呵呵!!!");   }}
 
运行结果:
Hello World!!!除数为0!除数为零后程序没有终止啊,呵呵!!!
 
4、运行时错误Error类及其子类表示运行时错误,通常是由Java虚拟机抛出的,JDK中与定义了一些错误类,比如VirtualMachineError和OutOfMemoryError,程序本身无法修复这些错误.一般不去扩展Error类来创建用户自定义的错误类。而RuntimeException类表示程序代码中的错误,是可扩展的,用户可以创建特定运行时异常类。Error(运行时错误)和运行时异常的相同之处是:Java编译器都不去检查它们,当程序运行时出现它们,都会终止运行。
5、最佳解决方案        对于运行时异常,我们不要用try...catch来捕获处理,而是在程序开发调试阶段,尽量去避免这种异常,一旦发现该异常,正确的做法就会改进程序设计的代码和实现方式,修改程序中的错误,从而避免这种异常。捕获并处理运行时异常是好的解决办法,因为可以通过改进代码实现来避免该种异常的发生。
        对于受检查异常,没说的,老老实实去按照异常处理的方法去处理,要么用try...catch捕获并解决,要么用throws抛出!对于Error(运行时错误),不需要在程序中做任何处理,出现问题后,应该在程序在外的地方找问题,然后解决。
 
六、异常转型和异常链
异常转型在上面已经提到过了,实际上就是捕获到异常后,将异常以新的类型的异常再抛出,这样做一般为了异常的信息更直观!比如:public void run() throws MyException{ ... try{  ... }catch(IOException e){  ...  throw new MyException(); }finally{  ... }}
 
        异常链,在JDK1.4以后版本中,Throwable类支持异常链机制。Throwable 包含了其线程创建时线程执行堆栈的快照。它还包含了给出有关错误更多信息的消息字符串。最后,它还可以包含 cause(原因):另一个导致此 throwable 抛出的 throwable。它也称为异常链 设施,因为 cause 自身也会有 cause,依此类推,就形成了异常链,每个异常都是由另一个异常引起的。         通俗的说,异常链就是把原始的异常包装为新的异常类,并在新的异常类中封装了原始异常类,这样做的目的在于找到异常的根本原因。
        通过Throwable的两个构造方法可以创建自定义的包含异常原因的异常类型:Throwable(String message, Throwable cause)           构造一个带指定详细消息和 cause 的新 throwable。 Throwable(Throwable cause)           构造一个带指定 cause 和 (cause==null ? null :cause.toString())(它通常包含类和 cause 的详细消息)的详细消息的新 throwable。 getCause()           返回此 throwable 的 cause;如果 cause 不存在或未知,则返回 null。initCause(Throwable cause)           将此 throwable 的 cause 初始化为指定值。
        在Throwable的子类Exception中,也有类似的指定异常原因的构造方法:Exception(String message, Throwable cause)           构造带指定详细消息和原因的新异常。 Exception(Throwable cause)           根据指定的原因和 (cause==null ? null : cause.toString()) 的详细消息构造新异常(它通常包含 cause 的类和详细消息)。 因此,可以通过扩展Exception类来构造带有异常原因的新的异常类。
 
七、Java异常处理的原则和技巧
 
1、避免过大的try块,不要把不会出现异常的代码放到try块里面,尽量保持一个try块对应一个或多个异常。2、细化异常的类型,不要不管什么类型的异常都写成Excetpion。3、catch块尽量保持一个块捕获一类异常,不要忽略捕获的异常,捕获到后要么处理,要么转译,要么重新抛出新类型的异常。4、不要把自己能处理的异常抛给别人。5、不要用try...catch参与控制程序流程,异常控制的根本目的是处理程序的非正常情况。

)finally清理

对于一些代码,无论try有无抛出,都希望执行,为了这样可以在异常处理最后加上finally语句。印象最深的是与数据库打交道的时候,finally永远要记得回收Connection等等。

这样无论异常是否抛出,finally子句总能被执行。这样就不怕发生内存中的资源一直占用的情况,印象深刻的还有老师讲的,公司一个新来的忘记写finally语句,后面内存的东西越来越多,整个系统死掉了。


有一个特别的是,try中有return语句,还是会继续执行finally里的语句。


缺憾:异常丢失

class ImportantException extends Exception{     public String toString(){         return "import";     } } class LightException extends Exception{     public String toString(){         return "light";     } }  public class LoseException {     static void i() throws ImportantException{         throw new ImportantException();     }     static void l() throws LightException{         throw new LightException();     }     public static void main(String[] args) {         try{             try {                 i();              }finally{                 l();}          }catch (Exception e) {             e.printStackTrace();         }     } }

important的异常还是丢失了,虽然书上的作者早已希望未来的版本会修正这个问题,但是目前版本7还是没改善啊。
另外,finally子句只用return也会丢失异常。


2)构造器问题

这个问题比较复杂,看了好久,和之前处理的不一样。

书上的原话,构造器会把对象设置成安全的初始状态,但是会有别的动作,比如打开一个文件,这样的动作只有在对象使用完毕并且用户调用了特殊的清理方法之后才清理。

为什么说和以前不一样,以前确实是像作者所说,用finally最后处理就行了。但是,构造器在创建的时候就失败呢?


<span style="font-size:18px;">public class TestConstructor {     private BufferedReader bf;     public TestConstructor(String name) throws Exception{         try {             bf = new BufferedReader(new FileReader(name));         } catch (FileNotFoundException e) {             System.out.println("can not open!");             //其实还没打开,不需要关闭             throw e;         } catch(Exception e){             try{                 bf.close();             }catch(IOException ie){                 System.out.println("close faild");             }         }finally{             //不要在这里关闭         }     }          public String getLine() {         String s;         try {           s = bf.readLine();         } catch(IOException e) {             throw new RuntimeException("getLine failed!");         }         return s;       }          public void dispose(){         try {             bf.close();             System.out.println("dispose successful");         } catch (IOException e) {             throw new RuntimeException("getLine failed!");         }     } }</span>


io流,构造器传参之后能把FileReader和BufferedReader合在一起,看不懂的话可以提前翻一下io流的内容。FileNotFoundException,其实文件都找不到,我们是不用进行清理的,因为finally在每次构造器完成都执行一遍,不能把关闭的语句写在这里,为什么和以前把清理的代码写在finally中不同呢?

目的不同,在这里我们需要TestConstructor对象在整个生命周期都处于打开状态。而之前的那些我们希望的是快点用完快点关闭。



还有一个特别的地方,本来String是要初始化的,但是因为异常的抛出可以不用初始化。真正不用TestConstructor的时候,就调用dispose方法清除。


<span style="font-size:18px;">public class Clean {     public static void main(String[] args) {         try {             TestConstructor tc = new TestConstructor("a.txt");             try{                 String s ;                 int i ;                 while((s = tc.getLine())!=null){                     //自己要进行的字符处理                 }             }catch(Exception e){                 e.printStackTrace();             }finally{                 tc.dispose();             }         } catch (Exception e) {             e.printStackTrace();         } finally{             System.out.println("failed constructed!");         }     } }</span>

这种嵌套是要自己去写的,Eclipse不给提示,因为语句已经在一个try块里面了。仔细分析一下,这个嵌套写的很漂亮,因为构造失败的话,我们根本就不需要去关闭TestConstructor,相反,构造成功的话,我们最后记得要清理,这和上面红字的地方是对应的,TestConstructor是读取文件的,我们确实需要再整个生命周期里面打开,而Clean这个类是调用Testconstructor这个类的,当成功构造和使用完读取字符串之后,自然而然就用dispose关闭,只不过我以前写程序的时候没有注意到,finally的处理和try,catch的嵌套可以写得这么美,真让人拍案叫绝。


3)异常匹配

这个问题比较简单,异常抛出后,找到匹配的处理程序后,就认为异常将的到处理,不再继续寻找。

try { } catch (FileNotFoundException e) {    System.out.println("can not open!"); } catch(Exception e){ }
如果异常与第一个匹配,那就捕获,如果不是,就由Exception捕获,因为Exception会捕获Exception以及其他从它派生的异常。

其他可选方式:

异常处理一个原则是“只有你知道如何处理才去捕获异常”,前面一篇文章已经说过,异常其实是为了将错误发生的地点的代码和处理错误的代码分开,让你专心写前者的代码。


作者自己也曾经犯过错误:


    try{     }catch(Exception e){}
称为:harmful if swallowed,吞食有害,异常有,因为catch是必须加上的,但是有没有进行处理,就这样被吞了。所以,要么写好错误处理,要么抛给高层。
4)历史与观点

历史就不说了,书上太长了。

说一下一个CLU设计师的观点——我们觉得强迫程序员在不知道该采取什么错事的时候提供处理程序是不现实的。但是我们也知道,放在那里不理也是不现实的,这是我自己的话,所以异常是个好东西。


总结:异常让我们可以集中精力解决问题,就是问题和业务逻辑的代码分开后再集中处理。

摘抄一下书上的异常使用指南:

把当前运行环境能做的事情尽量做完,然后把不同或者相同的异常抛向更高层。

让类库和程序更安全。

作为程序设计不可分割的一部分,要学会使用。


Java的基本理念是“结构不佳的代码不能运行”
发现错误的理想时机是在编译阶段,然而,编译期间并不能找出所有的错误,余下的问题必须在运行期间解决。
1.概念
错误处理的解决方法是:用强制规定的形式来消除错误处理过程中随心所欲的因素。
2.基本异常
异常情形是指阻止当前方法或作用域继续执行的问题。
普通问题是指在当前环境下能得到足够的信息,总能处理这个错误。
异常处理程序的任务是将程序从错误状态中恢复,以使程序能要么换一种方式运行,要么继续运行下去。
2.1异常参数
所有标准异常类都有两个构造器:一个是默认构造器,另一个是接受字符串作为参数,以便能把相关信息放入异常对象的构造器
能够抛出任意类型的Throwable对象,它是异常类型的根类
3.捕获异常
监控区域:是一段可能产生异常的代码,并且后面跟着处理这些异常的代码
3.1try块
try块:在这个块里“尝试”各种(可能产生异常的)方法调用,它是跟在try关键字之后的普通程序块。
3.2异常处理程序
抛出的异常必须在某处得到处理,这个“地点”就是异常处理程序
针对每个要捕获的异常,得准备相应的处理程序。异常处理程序紧跟在try块之后,以关键字catch表示。
注意:只有匹配的catch子句才能得到执行。在try块的内部,许多不同的方法调用可能会产生类型相同的异常,而你只需要
提供一个针对此类型的异常处理程序。
终止和恢复
异常处理理论上有两种基本模型
终止模型:在这种模型中,将假设错误非常关键,以至于程序无法返回到异常发生的地方继续执行。一旦异常被抛出。就表明
错误已无法挽回,也不能回来继续执行。
恢复模型:异常处理程序的工作是修正错误,然后重新尝试调用出问题的方法,并认为第二次能成功。
程序员更倾向于终止模型,恢复模型不适用,原因是它所导致的耦合:恢复性的处理程序需要了解异常抛出的地点,这势必要
包含依赖于抛出位置的非通用性代码。这增加了代码编写和维护的困难,对于异常可能会从许多地方抛出的大型程序来说,更是如此。
4.创建自定义异常
不必拘泥于Java中已有的异常类型。java提供的异常体系不可能预见所有的希望加以报告的错误,所以可以自己定义异常类
来表示程序中可能遇到的待定问题。
5.异常说明
异常说明使用了附加的关键字throws,后面接一个所有潜在异常类型的列表
代码必须与异常说明保持一致。如果方法里的代码产生异常却没有进行处理,编译器会发现这个问题并提醒你:要么处理这个
异常,要么就在异常说明汇总表明此方法将产生异常。
注:可以声明方法将抛出异常,实际上却不抛出。
6.捕获所有异常
通过捕获异常类型的基类Execption,可以捕获所有的异常,所以最好把它放在处理程序列表的末尾,以防止它抢在其他程序之前先把异常捕获。
因为Execption是与编程有关的所有异常类的基类,所以它不会含有太多具体的信息,不过可以调用它从其基类Throwable继承的
方法:
String getMessage()
String getLocalizedMessage()
6.1栈轨迹
void printStackTrace()返回一个由栈轨迹中的元素所构成的数组。
6.2重新抛出异常
重抛异常会把异常抛给上一级环境中的异常处理程序,同一个try块的后续catch子句将被忽略。
如果只是把当前异常对象重新抛出,那么printStackTrace()方法显示的将是原来异常抛出点的调用栈信息,而非重新抛出点信息
要想更新这个信息,可以调用fillInStackTrace()方法,这将返回一个Throwable对象。它把当前栈信息填入原来那个异常对象中
6.3异常链
常常会想要在捕获一个异常后抛出另一个异常,并且希望把原始异常的信息保存下来,这被称为异常链
Throwable的子类在构造器中可以接受一个cause(因由)对象作为参数。这个cause就用来表示原始异常,这样通过把原始异常
传递给新的异常,使得即使在当前位置创建并抛出了新的异常,也能通过这个异常链追踪到异常最初发生的位置。
在Throwable的子类中,只有三种基本的异常类提供了带cause参数的构造器,它们是Error、Exceprion以及RuntimeExecption。
如果要把其他类型的异常连接起来,应该使用initCause()方法而不是构造器。
7.Java标准异常
Throwable这个Java类被用来表示任何可以作为异常被抛出的类。Throwable对象可分为两种类型:Error用来表示编译时和系统
错误;Execption是可以被抛出的基本类型。
异常的基本的概念是用名称代表发生的问题,并且异常的名称应该可以望文知意。
7.1特例RuntimeExecption
运行时异常的类型很多,它们会自动被Java虚拟机抛出,所以不必在异常说明中把它们列出来。
8.使用finally进行清理
无论异常是否被抛出,finally子句总能被执行
8.1finally用来做什么
当要把除内存之外的资源恢复到它们的初始状态时,就要用到finally子句。
当涉及break和continue语句的时候,finally子句也会得到执行
8.2在return中使用finally
因为finally子句总是会执行的,所以在一个方法中,可以从多个点返回,并且保证重要的清理工作仍旧会执行
8.3缺憾:异常丢失
遗憾的是,Java的异常实现也有瑕疵。异常作为程序出错的标志,决不应该被忽略,但是还是有可能被忽略。
用某些特殊的方式使用finally子句,就会发生这种情况。
9.异常的限制
当覆盖方法的时候,只能抛出在基类方法的异常说明里列出的那些异常。
异常限制对构造器不起作用。子类构造器可以抛出任何异常,而不必理会基类构造器所抛出的异常。派生类构造器的异常说明
必须包含基类构造器的异常说明。
10.构造器
构造器会把对象设置成安全的初始状态,但还会有别的动作,比如打开一个文件,这样的动作只有在对象使用完毕并且用户调用
了特殊的清理方法之后才能得以清理。如果在构造器内抛出了异常,这些清理行为也许就不能正常工作。
11.异常匹配
抛出异常的时候,异常处理系统会按照代码的书写顺序找出“最近”的处理程序。找到匹配的处理程序之后,它就认为异常将
得到处理,然后就不再继续查找。查找的时候并不要求抛出的异常同处理程序所声明的异常完全匹配。派生类的对象也可以
匹配其基类的处理程序。
12.其他可选方式
异常处理的原则:只有在你知道如何处理的情况下才捕获异常

12.1 概念

把“描述在正常执行过程中做什么事”的代码和“出了什问题怎么办”的代码相分离。

12.2 基本异常

12.2.1 异常参数

12.3 捕获异常

12.3.1 try块

完成任务的代码没有与错误检查的代码混在一起。

12.3.2 异常处理程序

终止模式与恢复模式。

可以把try块放在while循环里,这样就不断地进入try块,知道得到满意的结果。

12.4 创建自定义异常

要自己定义异常类,必须从已有的异常类继承,最好是选择意思相近的异常类继承。创建新的异常类型最简单的方法就是让编译器为你产生默认构造器。

12.4.1 异常与记录日志

对于异常类来说,getMessage()方法有点类似于toString()方法。

12.5 异常说明

异常说明使用了附加的关键字throws,后面接一个所有潜在异常类型的列表。

12.6 捕获所有异常

12.6.1 栈轨迹

getStackTrace()方法将返回一个由栈轨迹中的元素所构成的数组,其中每一个元素都表示栈中的一帧。元素0是栈顶元素,并且是调用序列中的最后一个方法调用。数组中的最后一个元素和栈底是调用序列中的第一个方法调用。

12.6.2 重新抛出异常

重新抛出异常会把异常抛给上一级环境中的异常处理程序,同一个try块的后续catch子句将被忽略。

可以调用fillInStackTrace()方法更新原来异常抛出点的调用栈信息。

12.6.3 异常链

12.7 Java标准异常

Throwable对象可分为两种类型(只从Throwable继承而得到的类型):Error用来表示编译时和系统错误(除特殊情况外,一般不用你关心);Exception是可以被抛出的基本类型,在java库,用户方法以及运行时故障中都可能抛出Exception型异常.所以java程序员关心的基类型通常是Exception。

12.7.1 特例:RuntimeException

12.8 使用finally进行清理

finally子句总能被执行。

12.8.1 finally用来做什么

当涉及break和continue语句的时候,finally子句也会得到执行。如果把finally子句和带标签的break及continue配合使用,在java里就没必要使用goto语句了。

12.8.2 在return中使用finally

在一个方法中可以从多个点返回。

12.8.3 缺憾:异常丢失


12.9 异常的限制

当覆盖方法时,只能抛出在基类方法的异常说明里列出的那些异常。


异常限制对构造器不起作用,派生类构造器可以抛出任何异常,派生类构造器的异常说明必须包含基类构造器的异常说明。


12.10 构造器

在创建需要清理的对象之后,立即进入一个try-finally语句块。


12.11 异常匹配

基类异常会捕获基类本身以及所有从它派生的异常。


12.12 其他可选方式
12.12.1 历史
12.12.2 观点





 















猜你喜欢

转载自zhyp29.iteye.com/blog/2306972