java异常(编程思想)

通过异常处理错误
    java的基本理念是“结构不佳的代码不能运行”
    发现错误的理想时机是在编译阶段,也就是在你试图运行程序之前。然而编译期间并不能找出所有的错误,余下的问题必须在运行间解决。这就需要错误源能通过某种方式,把适当的信息传递给某个接受者————接受者将指导如何正确处理这个问题。
    
    概念
        C以及其他早期语言常常具有多种错误处理模式,这些模式往往建立在约定俗成的基础之上,而并不属于语言的一部分。通常会返回某个特殊值或者设置某个标志,并且假定接受者将堆这个返回值或者标志进行检查,以判定是否发生了错误。然而,人们发现,高傲的程序员们在使用程序库的时候更倾向于认为:”对,错误也许会发生,但那是别人造成的,不关我的事“。所以,程序员不去检查错误情形也就不足为奇了。如果的确在每次调用方法的时候都彻底地进行错误检查,代码很可能会变得难以阅读。正式由于程序员还仍然用这些方式拼凑系统,所以他们拒绝承认这样的一个事实:对于构造大型、健壮、可维护的程序而言,这种错误处理模式已经成为了主要障碍。
        解决的办法是,用强制规定的形式来消除错误处理过程中随心所欲的因素。
        “异常”这个词有“我对此感到意外”的意思。问题出现了,你也许不清楚该如何处理,但你的确知道不应该置之不理;你要停下来,看看是不是有别人在别的地方,能够处理这个问题。只是在当前的环境中还没有足够的信息来解决这个问题,所以就把这个问题提交到一个更高级别的环境中,在这里将作出正确的决定。
        使用异常所带来的另一个相当明显的好处是,它往往能够降低错误处理代码的复杂度。如果不使用异常,那么就必须检查特定的错误,并在程序的许多地方处理它
        
    基本异常
        异常情形(exceptional condition)是指阻止当前方法或作用域继续执行的问题。把异常情形和普通问题相区分很重要。普通问题是指,在当前环境下能得到足够的信息,总能处理这个错误。而对于异常情形,就不能继续下去了,因为在当前环境下无法获取必要的信息来解决问题。你所能做的就是从当前环境跳出,并且把问题提交给上一级环境。这就是抛出异常时所发生的事。
        当抛出异常后,有几件事会随之发生。首先,同Java中其他对象的创建一样,将使用new在堆上创建异常对象。然后,当前的执行路径被终止,并且从当前环境中弹出对异常对象的引用。此时,异常处理机制接管程序,并开始寻找一个恰当的地方来继续执行程序。这个恰当的地方就是异常处理程序,它的任务是将程序从错误状态中恢复,以使程序能要么换一种方式运行,要么继续运行下去。
        举一个抛出异常的简单例子。对于对象引用t,传给你的时候可能尚未被初始化。所以在使用这个对象调用其方法之前,会先对引用进行检查。可以创建一个代表错误信息的对象,并且将它从当前环境中"抛出",这样就把错误信息传播到了"更大"的环境中。这被称为抛出一个异常,看起来像是这样:
            if(t == null) {
                throw new NullPointerException();
            }
        这就抛出了异常,于是在当前环境下就不必要再为这个问题操心了,它将在别的地方得到处理。
        异常使得我们可以将每件事当作一个事务来考虑,而异常可以看护着这些事务的底线。我们还可以将异常看做是一种内建的恢复系统,因为我么在程序中可以拥有各种不同的恢复点。如果程序的某部分失败了,异常将“恢复”到程序中某个已知的稳定点上。
        异常最重要的方面之一就是如果真的发生问题,它们不允许程序沿着其正常的路径继续走下去。
        
    异常参数
        与适用Java中的其他对象一样,我们总是用new在堆上创建异常对象,这也伴随着存储空间的分配和构造器的调用。所有的标准异常类都有两个构造器:一个是默认构造器;另一个是接受字符串作为参数,以便能将相关参数放入到异常对象的构造器:
            throw new NullPointerException("t = null");
        要把这个字符串的内容提取出来可以有多种不同的方法。
        关键字throw将产生许多有趣的结果。在使用new创建了异常对象之后,此对象的引用将传送给throw。尽管返回的异常对象其类型通常与方法设计的返回的类型不同,但从效果上来看,它就是从方法返回放入。可以简单地将异常看做一种不同的返回机制。另外还能用抛出异常的方式从当前的作用域退出。在这两种情况下,将会返回一个异常对象,然后退出方法或作用域。
        抛出异常与方法正常返回的相似之处到此为止。因为异常返回的“地点”与普通方法调用返回的“地点”完全不同。(异常将在一个恰当的异常处理程序中得到解决,它的位置可能离异常被抛出的位置很远,也可能会跨越方法调用栈的许多层次)
        此外,能够抛出任意类型的Throwable对象,它是异常类型的根本、通常,对于不同类型的错误,要抛出对应的异常。错误信息可以保存在异常对象内部或者用异常类的名称来暗示。上一层环境通过这些信息来决定如何处理异常。
        
    捕获异常
        监控区域(guarded region)。是一段可能产生异常的代码,并且后面跟这处理这些异常的代码。
        try块
            这个块里面尝试各种(可能产生异常的)方法调用,所以称为try块。它是跟在try关键字之后的普通程序块:
                try{
                    //Code that might generate exceptions
                }
            对于不支持异常处理的程序语言,要想仔细检查错误,就得在每个方法的调用的前后加上设置和错误检查的代码,甚至在每次调用同一个方法的时候也得这样做。有了异常处理机制,可以将所有动作放在try里面,然后只需要在一个地方就可以捕获所有异常。
        异常处理程序
            当然,抛出的额异常必须在某处得带处理。这个地点就是异常处理程序,而且针对每个要捕获的异常,得准备相对应的处理程序。异常程序紧跟在try块之后,以关键字catch表示:
                try{
                    //Code that might generate exceptions 
                } catch(Type1 id1) {
                    //Handle exceptions of Type1
                } catch(Type id2){
                    // Handle exceptions of Type2
                }
                //etc ...
            每个catch子句看起来就像是接受一个切仅接受一个特殊类型的参数的方法。可以在处理程序的内部使用标识符号(id1, id2等等),这与方法参数的使用很相似。有时可能用不到标识符,因为异常的类型已经给了你足够的信息来对异常进行处理,但标识符不能省略。
            异常处理程序必须紧跟在try块之后。当异常被抛出时,异常处理机制将负责搜寻参数与异常类型相匹配的第一个处理程序。然后进入catch子句执行,此时认为异常得到了处理。一旦catch子句结束,则处理程序的查找过程结束。注意,只有匹配的catch子句才能得到执行,这与switch语句不通,switch语句需要在每个case后面紧跟一个break,以避免执行后续的case子句。
        终止与恢复
            异常处理理论上有两种基本模型。
                Java支持终止模型。在这种模型中,将假设错误非常关键,以至于程序无法返回到异常发生的地方继续执行。一旦异常被抛出,就表明错误已经无法挽回,也不能回来继续执行。
                恢复模型。意思是异常处理程序的工作是修复错误,然后重新尝试调用出问题的方法,并任务第二次能成功。如果想要用Java实现类似恢复的行为,那么在遇见错误的时候就不能排除异常,而是调用方法来修正该错误。或者,把try块放到while循环里,这样就不断进入try块,知道得到满意的结果。
    创建自定义异常    
        要自己定义异常类,必须从已有的异常类继承,最好是选择意思相近的异常类继承。建立新的异常类型最简单的方法就是让编译器为你产生默认构造器,所以这几乎不用写多少代码:
            class SimpleException extends Exception {}
            public class InheritingExceptions {
                public void f() throws SimpleException {
                    System.out.println("Throw SimpleException from f()");
                    throw new SimpleException();
                }
                public static void main(String[] args) {
                    InheritingExceptions sed = new InheritingExceptions();
                    try{
                        sed.f();
                    } catch(SimpleException e) {
                        System.out.println("Cautht it!");
                    }
                }
            }
        编译器创建了默认后早期,它将自动调用基类的默认构造器。
        例子中的结果被打印到了控制台上,但是也许你想通过Systemn.err而将错误发送给标准错误流。通常这笔把错误输出到System.out要好,因为System.out也许会被重定向。但是如果把结果送到System.err,它就不会随System.out一起被重定向,这样更容易被用户注意:
            class MyException extends Exception {
                public MyException() {}
                public MyException(String message) {
                    super(message);
                }
            }
            public class FullConstructors {
                public static void f() throws MyException {
                    System.out.println("Throwing MyException from f()");
                    throw new MyException();
                }
                public static void g() throws MyException {
                    System.out.println("Throwing MyException from g()");
                    throw new MyException("Originated in g()");
                }
                public static void main(String[] args) {
                    try{
                        f();
                    } catch(MyException e) {
                        e.printStackTrace(System.out);
                    }
                    try{
                        g();
                    } catch(MyException e) {
                        e.printStackTrace(System.out);
                    }
                }
            }
            两个构造器定义了MyException类型对象的创建方式。第二个构造器使用super关键字明确调用了其父类构造器,接受一个字符串作为参数。
            在异常处理程序中,调用了在Throwable声明的printStackTrace()方法。就像输出中看到的,它将打印“聪明和方法调用处直到异常抛出处”的方法序列。这里信息被发送到了System.out,并被自动被捕获和显示在输出中。但是如果调用默认版本:
                e.printStackTrace();
            则信息将被输出到标准错误流。
        异常与记录日志
            你可能还想使用java.util.logging工具将输出记录到日志中。
            class LoggingException extends Exception {
                private static Logger logger = Logger.getLogger("LoggingException");
                public LoggingException () {
                    StringWriter trace = new StringWriter();
                    printStackTrace(new PrintWriter(trace));
                    logger.severe(trace.toString());
                }
            }
            public class LoggingExceptions {
                public static void main(String[] args) {
                    try {
                        throw new LoggingException();
                    } catch (LoggingException e) {
                        System.err.println("Caught " + e);
                    }
                    try{
                        throw new LoggingException();
                    }catch(LoggingException e) {
                        System.err.println("Caught " + e);
                    }
                }
            }
            静态的Logger.getLogger()方法创建了一个String参数相关联的Logger对象,这个Logger对象会将其输出发送到熬System.err。向Logger写入的足以简单的方式就是直接调用与日志记录消息级别相关联的方法,这里使用的是secere().为了产生日志记录消息,我们想要获取异常抛出处的栈轨迹,但是pringStackTrace()不会默认的产生字符串。为了获取字符串,我们需要使用重载的printStackTrace()方法,它接受一个java.io.PrintWriter对象作为参数。如果我们将一个java.io,PrintWriter对象传递给这个PringWriter的构造器,那么通过调用toString()方法,就可以将输出抽取为一个String
            
    异常说明
        Java鼓励人们把方法可能会抛出的异常告知使用此方法的客户端程序员。这是种优雅的做法,它使得调用者能确切知道写什么样的代码可以获取所有潜在的异常。为了预防这样的问题,java提供了相对应的语法(并强制使用这个语法),使你能以礼貌的方式告知客户端程序员某个方法可能会抛出的异常类型,然后客户端程序员就可以进行相应的处理。这就是异常说明,它属于方法声明的一部分,紧跟在形式参数后面。
        异常说明使用了附加的关键字throws,后面接一个所有潜在异常类型的列表。出了从RuntimeException继承的异常,它们可以在没有异常说明的情况下被抛出。
        不过还是有个能“作弊”的功能:可以声明方法将抛出异常,实际上却不抛出。编译器相信了这个位置,并强制此方法的用户像真的抛出异常那样使用这个方法。这样做的好处是,为异常先占个位置,以后就可以抛出这种异常而不用修改已有的代码。在定义抽象基类和接口时这种能力很重要,这样派生类或者接口实现就能够抛出这些预先声明的异常。
        这种在编译时吧被强制检查的异常被称为被检查的异常。
    捕获所有异常
        可以只写一个异常处理程序;来处理所有类型的异常。例如通过Exception。
        栈轨迹
            printStackTrace()方法所提供的信息可以通过getStackTrace()方法来直接访问,这个方法将返回一个由栈轨迹中的元素所构成的数组,其中每一个元素都表示栈中的一帧。元素0是栈顶元素,并且是调用虚列中的最后一个方法调用。
            public class WhoCalled {
                static void f() {
                    //Generate an exception to fill in the stack trace
                    try {
                        throw new Exception();
                    }catch (Exception e) {
                        for(StackTraceElement ste : e.getStackTrace()){
                            System.out.println("fileName: " + ste.getFileName());
                            System.out.println("className: " + ste.getClassName());
                            System.out.println("methodName: " + ste.getMethodName());
                            System.out.println("lineNumber: " + ste.getLineNumber());
                            System.out.println("---------------------------------------");
                        }
                    }
                }
                static void g() { f(); }
                static void h() { g(); }
                public static void main(String[] args) {
                    f();
            //        g();
            //        h();
                }
            }
            控制台:
            fileName: WhoCalled.java
            className: com.zhen.exceptions.t1.WhoCalled
            methodName: f
            lineNumber: 10
            ---------------------------------------
            fileName: WhoCalled.java
            className: com.zhen.exceptions.t1.WhoCalled
            methodName: main
            lineNumber: 24
            ---------------------------------------
            fileName: NativeMethodAccessorImpl.java
            className: sun.reflect.NativeMethodAccessorImpl
            methodName: invoke0
            lineNumber: -2
            ---------------------------------------
            fileName: NativeMethodAccessorImpl.java
            className: sun.reflect.NativeMethodAccessorImpl
            methodName: invoke
            lineNumber: 62
            ---------------------------------------
            fileName: DelegatingMethodAccessorImpl.java
            className: sun.reflect.DelegatingMethodAccessorImpl
            methodName: invoke
            lineNumber: 43
            ---------------------------------------
            fileName: Method.java
            className: java.lang.reflect.Method
            methodName: invoke
            lineNumber: 497
            ---------------------------------------
            fileName: AppMain.java
            className: com.intellij.rt.execution.application.AppMain
            methodName: main
            lineNumber: 147
            ---------------------------------------
        重新抛出异常
            有时候希望把刚捕获的异常重新抛出,尤其是在使用Exception捕获所有异常的时候。既然已经的刀客对当前异常对象的引用,可以直接把它重新抛出:
                catch(Exception e) {
                    System.out.println("An exception was thrown");
                    throw e;
                }
            重新抛出异常会把异常抛给上一级环境中的异常处理程序,同一个try块的后续catch子句将被忽略。此外,异常对象的所有信息都得以保持,所以高一级环境中捕获此异常的处理程序可以从这个异常对象中得到所有信息。
            如果只是把当前异常对象排除,那么printStackTrace()方法显示的将是原来异常抛出点的调用栈信息,而并非重新抛出点的信息。要想更新这个信息,可以调用fillInStackTrace()方法,这将返回一个Throwable对象,它是通过把当前调用栈信息填入原来那个异常对象而建立的。
            public class Rethrowing {
                public static void f() throws Exception {
                    System.out.println("originating the exception in f()");
                    throw new Exception("thrown from f()");
                }
                public static void g() throws Exception {
                    try{
                        f();
                    }catch(Exception e) {
                        System.out.println("Inside g(), e.printStackTrace()");
                        e.printStackTrace(System.out);
                        throw e;
                    }
                }
                public static void h() throws Exception {
                    try{
                        f();
                    }catch(Exception e) {
                        System.out.println("Inside h(), e.printStackTrace()");
                        e.printStackTrace(System.out);
                        throw (Exception) e.fillInStackTrace();
                    }
                }
                public static void main(String[] args) {
                    try{
                        g();
                    }catch(Exception e) {
                        System.out.println("main: printStackTrace()");
                        e.printStackTrace(System.out);
                    }
                    try{
                        h();
                    } catch(Exception e) {
                        System.out.println("main: printStackTrace()");
                        e.printStackTrace(System.out);
                    }
                }
            }
            再调用fillInStackTrace()的哪一行就成了异常的新发生地了。
            有可能在捕获异常之后抛出另一种异常。这么做的话,得到的效果类似于使用fillInStackTrace(),有关原来异常发生点的信息会丢失,剩下的是与新的抛出点有关的信息:
            class OneException extends Exception {
                public OneException(String s) {
                    super(s);
                }
            }
            class TwoException extends Exception {
                public TwoException(String s) {
                    super(s);
                }
            }
            public class RethrowNew {
                public static void f() throws OneException {
                    System.out.println("originating the exception in f()");
                    throw new OneException("thrown from f()");
                }
                public static void main(String[] args) {
                    try{
                        try{
                            f();
                        } catch(OneException e) {
                            System.out.println("Caughr in inner try, e.printStackTrace()");
                            e.printStackTrace(System.out);
                            throw new TwoException("from inner try");
                        }
                    } catch(TwoException e) {
                        System.out.println("Caught in outer try. e.printStackTrace()");
                        e.printStackTrace(System.out);
                    }

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

            public class DynamicFields {
                /*使用数组存储字符的key与value*/
                private Object[][] fields;
                /*初始化容器,将内容填充null*/
                public DynamicFields(int initialSize) {
                    fields = new Object[initialSize][2];
                    for(int i = 0; i < initialSize; i++){
                        fields[i] = new Object[] { null, null};
                    }
                }
                public String toString() {
                    StringBuilder result = new StringBuilder();
                    for(Object[] obj : fields){
                        result.append(obj[0]);
                        result.append(": ");
                        result.append(obj[1]);
                        result.append("\n");
                    }
                    return result.toString();
                }
                /*查找指定内容在数组容器中是否存在,存在的话返回该key,否则返回-1*/
                private int hasField(String id) {
                    for(int i = 0; i < fields.length; i++){
                        if(id.equals(fields[0])){
                            return i;
                        }
                    }
                    return -1;
                }

                /* 根据内容得到所在的key,不存在抛出异常*/
                private int getFieldNumber(String id) throws NoSuchFieldException {
                    int fieldNum = hasField(id);
                    if(fieldNum == -1){
                        throw new NoSuchFieldException();
                    }
                    return fieldNum;
                }
                /*新增字段,容器扩容,放到最后*/
                private int makeField(String id) {
                    for(int i = 0; i < fields.length; i++){
                        if(fields[i][0] == null){
                            fields[i][0] = id;
                            return i;
                        }
                    }
                    //No empty fields. Add one
                    Object[][] tmp = new Object[fields.length + 1][2];
                    for(int i = 0; i < fields.length; i++){
                        tmp[i] = fields[i];
                    }
                    for(int i = fields.length; i < tmp.length; i ++) {
                        tmp[i] = new Object[]{null, null};
                    }
                    fields = tmp;
                    return makeField(id);
                }
                public Object getField(String id) throws NoSuchFieldException {
                    return fields[getFieldNumber(id)][1];
                }
                public Object setField(String id, Object value) throws DynamicFieldsException{
                    if(value == null) {
                        //Most exceptions don't have a "cause" constructor.
                        //In these cases you must use initCause()
                        //available in all Throwable subclasses
                        DynamicFieldsException def = new DynamicFieldsException();
                        def.initCause(new NullPointerException());
                        throw def;
                    }
                    int fieldNumber = hasField(id);
                    if(fieldNumber == -1) {
                        fieldNumber = makeField(id);
                    }
                    Object result = null;
                    try{
                        result = getField(id);// get old value
                    } catch(NoSuchFieldException e) {
                        // Use constructor that takes "cause":
                        throw new RuntimeException(e);
                    }
                    fields[fieldNumber][1] = value;
                    return result;
                }

                public static void main(String[] args) {
                    DynamicFields df = new DynamicFields(3);
                    System.out.println(df);
                    try{
                        Object field = df.setField("d", null);
                        df.setField("d", "A value for d");
                        df.setField("number", 47);
                        df.setField("number2", 48);
                        System.out.println(df);
                        df.setField("d", "A new value for d");
                        df.setField("number3", 11);
                        System.out.println("df: " + df);
                        System.out.println("df.getField(\"id\")" + df.getField("d"));

                    } catch(NoSuchFieldException e) {
                        e.printStackTrace(System.out);
                    } catch(DynamicFieldsException e) {
                        e.printStackTrace(System.out);
                    }
                }
            }
    java标准异常
        Throwable这个Java类被用来表示任何可以作为异常被抛出的类。Throwable对象可以分为两种类型:Error用来表示编译时和系统错误;Exception是可以被抛出的基本类型。在Java类库、用户方法以及运行时故障中都可能抛出Exception型异常。所以Java程序员关心的基类型通常都是Exception。
        
        特例:RuntimeException
            if(t == null) {
                throw new NullPointerException();
            }
            如果必须对传递给方法的每个引用都检查其是否为null,这听起来确实吓人。幸运的是,这不必由你亲自来做,它属于Java的标准运行时检测的一部分。如果要对null引用进行调用,Java会自动抛出NullPointException异常。
            属于运行时异常的类型有很多,它们会自动被java虚拟机抛出,所以不必再异常说明中把它们列出来。这些异常都是从RuntimeException类继承而来,所以既体现了继承的有点,使用起来也方便。并且,也不再需要在异常说明中声明方法将抛出RuntimeException类型的异常
            RuntimeException是一个特例。对于这种异常类型,编译器不需要异常说明,其输出被报告给了System.err.所以,如果RuntimeException没有被捕获而直达main(),那么在程序退出前将调用异常的printStackTrace()方法。
            请务必记住:
                只能在代码中忽略RuntimeException类型的异常,其他类型异常的处理都是由编译器强制实施的。究其原因,RuntimeException代表的是编程错误:
                    1、无法预料的错误。比如你控制范围外传递进来的null引用
                    2、作为程序员,应该在代码中进行检查的错误。(比如对于ArrayIndexOutOfBoundsException,就得注意一下数组的大小了)
    使用finally进行清理
        对于一些代码,可能希望无论try块的异常是否抛出,它们都能得到执行。为了得到这个效果,可以在异常处理程序后面加上finally子句。
        class ThreeException extends Exception {}
            public class FinallyWorks {
                static int count = 0;
                public static void main(String[] args) {
                    while(true) {
                        try{
                            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;
                            }
                        }
                    }
                }
            }
        finally用来做什么
            对于没有垃圾回收和析构函数自动调用机制的语言来说,finally非常重要。它能使程序员保证:无论try块里面发生了什么,内存总能得到释放。但Java有垃圾回收机制,所以内存释放不再是问题。而且Java也没有够细函数可供调用。那么,Java在什么情况下才能用到finally呢?
            class Switch{
                private boolean state = false;
                public boolean read() { return state; }
                public void on() { state = true; System.out.println(this); }
                public void off() { state = false; System.out.println(this); }
                public String toString() { return state ? "on" : "off"; }
            }
            class OneOffException1 extends Exception {}
            class OneOffException2 extends Exception {}
            public class OneOffSwitch {
                private static Switch sw = new Switch();
                public static void f() throws OneOffException1, OneOffException2 {

                }
                public static void main(String[] args) {
                    try {
                        sw.on();
                        f();
                        sw.off();
                    } catch(OneOffException1 e) {
                        System.out.println("OneOffException1");
                        sw.off();
                    } catch(OneOffException2 e) {
                        System.out.println("OneOffException2");
                        sw.off();
                    }
                }
            }
            使用finally保证sw.off()一定执行:
            public class WithFinally {
                static Switch sw = new Switch();
                public static void main(String[] args) {
                    try {
                        sw.on();
                        OneOffSwitch.f();
                    } catch(OneOffException1 e) {
                        System.out.println("OneOffException1");
                    } catch(OneOffException2 e) {
                        System.out.println("OneOffException2");
                    } finally {
                        sw.off();
                    }
                }
            }
            甚至在异常都没有被当前的异常处理程序捕获的情况下,异常处理机制也会在跳到更高一层的异常处理程序之前,执行finally子句:
            class FourException extends Exception {}
            public class AlwaysFinally {
                public static void main(String[] args) {
                    System.out.println("Entering first try block");
                    try {
                        System.out.println("Entering second try block");
                        try{
                            throw  new FourException();
                        }finally{
                            System.out.println("finally in 2nd try block");
                        }
                    } catch(FourException e) {
                        System.out.println("Caught FourException in lst try block");
                    } finally {
                        System.out.println("finally in 1st try block");
                    }
                }
            }
        在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);
                    }
                }
            }
        缺憾:异常丢失
            遗憾的是,Java的异常实现也有瑕疵。异常作为程序出错的标志,绝不应该被忽略,但它还是有可能被轻易忽略。用某些特殊的方式使用finally子句,就会出现这种情况:
            class VeryImportantException extends Exception {
                public String toString() {
                    return "A very important exception!";
                }
            }
            class HoHumException extends Exception {
                public String toString() {
                    return "A trivial exception";
                }
            }
            public class LostMessage {
                void f() throws VeryImportantException {
                    throw new VeryImportantException();
                }
                void dispose() throws HoHumException {
                    throw new HoHumException();
                }
                public static void main(String[] args) {
                    LostMessage lm = new LostMessage();
                    try{
                        try{
                            lm.f();
                        }finally {
                            lm.dispose();
                        }
                    }catch (Exception e) {
                        System.out.println(e);
                    }

                }
            }
            一种更加简单的丢失异常的方式就是从finally子句中返回:
            public class ExceptionSilencer {
                public static void main(String[] args) {
                    try {
                        throw new RuntimeException();
                    } finally {
                        //Using return inside the finally block will silence any thrown exception
                        return;
                    }
                }
            }
    异常的限制
        在覆盖方法的时候,只能抛出在基类方法的异常说明里列出的那些异常
        异常限制对构造去哦去不起作用。派生类的构造器的异常说明必须包含基类构造器的异常说明
        class BaseballException extends Exception {}
            class Foul extends BaseballException {}
            class Strike extends BaseballException {}
            abstract class Inning {
                public Inning() throws BaseballException {}
                public void event() throws BaseballException {
                    //Doesn't actually have to throw anything
                }
                public abstract void atBat() throws Strike, Foul;
                public void walk() {}
            }
            class StormException extends Exception {}
            class RainedOut extends StormException {}
            class PopFoul extends Foul {}
            interface Storm {
                public void event() throws RainedOut;
                public void rainHard() throws RainedOut;
            }
            public class StormyInning  extends Inning implements Storm{
                //OK to add new exceptions for constructors ,but you must deal with the base constructor exceptions
                public StormyInning() throws RainedOut, BaseballException {}
                public StormyInning(String s) throws Foul, BaseballException {}
                // Regular method must conform to base class
               // void walk() throws PopFoul {} // Compile error
                //Interface CANNOT add exceptions to existing method from the base class
                //public void event() throws RainedOut{}
                //if the method doesn't alerady exist in the base class , the exception is OK
                public void rainHard() throws RainedOut {}
                // You can choose to not throw any exceptions , even if the base version does
                public void event() {}
                // Overriden methods can throw inherited exceptions
                public void atBat() throws PopFoul {}
                public static void main(String[] args) {
                    try {
                        StormyInning si = new StormyInning();
                        si.atBat();
                    }catch(PopFoul e) {
                        System.out.println("Pop foul");
                    }catch(RainedOut e){
                        System.out.println("Rained out");
                    }catch (BaseballException e) {
                        System.out.println("Generic baseball exception");
                    }
                    //Strike not thrown in derived version
                    try {
                        //Whar happens if you upcast
                        Inning i = new StormyInning();
                        i.atBat();
                        //you must catch the exceptions from the base-class version of method
                    } catch (Strike e) {
                        System.out.println("Strike");
                    } catch(Foul e) {
                        System.out.println("Foul");
                    } catch (RainedOut e) {
                        System.out.println("Rained Out");
                    } catch(BaseballException e) {
                        System.out.println("Generic baseball exception");
                    }
                }
            }
    异常匹配
        抛出异常的时候,异常处理系统会按照代码的书写顺序找出“最近”的处理程序。找到匹配的处理程序之后,它就认为异常将得到处理,然后就不再继续查找。
        查找的时候并不要求抛出的异常同处理程序所声明的异常完全匹配。派生类的对象也可以匹配其基类的处理程序。有意思的一个东西,父类总是可以涵盖子类。
        class Annoyance extends Exception {}
        class Sneeze extends Annoyance {}
        public class Human {
            public static void main(String[] args) {
                try{
                    throw new Sneeze();
                } catch(Sneeze s){
                    System.out.println("Caught Sneeze");
                } catch(Annoyance a) {
                    System.out.println("Caught Annoyance");
                }

                try{
                    throw new Sneeze();
                } catch(Annoyance a) {
                    System.out.println("Caught Annoyance");
                }
            }
        }
    其他可选方式
        “被检查的异常”使得这个问题变得复杂,因为它们强制你在可能还没准备好处理错误的时候被迫加上catch子句,这就导致了吞食则有害(harmful is swallowed)的问题:
        try{
            //
        } catch(ObligatoryException e) {}
        1、将异常传递给控制台:
            从main方法抛出
        2、将被检查的异常转换为不检查的异常
    异常使用指南:
        1、在恰当的级别处理问题
        2、解决问题并且重新调用产生异常的方法
        3、进行少许修补,然后绕过异常发生的地方继续执行
        4、用别的数据进行计算,以代替方法预计会返回的值
        5、把当前运行环境下能做的事情尽量做完,然后把相同的异常重抛到更高层
        6、把当前运行环境下能做的事情尽量做完,然后把不同的异常跑到更高层
        7、终止程序      

 

posted @ 2017-08-22 14:32  guodaxia  阅读(264)  评论(0编辑  收藏  举报