Java编程思想学习笔记(12)

Java编程思想学习笔记(12)

异常

Java的基本理念是“结构不佳的代码不能运行”。

发现错误的理想时机是在编译阶段,也就是在你试图运行程序之前。然而,编译期间并不能找出所有的错误,余下的问题必须在运行期间解决。这就需要错误源能通过某种方式,把适当的信息传递给某个接收者一-该接收者将知道如何正确处理这个问题。

改进的错误恢复机制是提供代码健壮性的最强有力的方式。错误恢复在我们所编写的每一个程序中都是基本的要素,但是在Java中它显得格外重要,因为Java的主要目标之一就是创建供他人使用的程序构件。要想创建健壮的系统,它的每一个构件都必须是健壮的。Java使用异常来提供一致的错误报告模型,使得构件能够与客户端代码可靠地沟通问题。

“异常” 这个词有“ 我对此感到意外" 的意思。问题出现了,你也许不清楚该如何处理,但
你的确知道不应该置之不理1 你要停下来,看看是不是有别人或在别的地方,能够处理这个问题。只是在当前的环境中还没有足够的信息来解决这个问题,所以就把这个问题提交到一个更高级别的环境中,在这里将作出正确的决定。

基本异常

**异常情形(exceptionalcondition)是指阻止当前方法或作用域继续执行的问题。**把异常情形 与普通问题相区分很重要,所谓的普通问题是指,在当前环境下能得到足够的信息,总能处理这个错误。而对千异常情形,就不能继续下去了,因为在当前环境下无法获得必要的信息来解决问题。你所能做的就是从当前环境跳出,并且把问题提交给上一级环境。这就是抛出异常时所发生的事情。

除法就是一个简单的例子。除数有可能为0,所以先进行检查很有必要。但除数为0代表的究竟是什么意思呢?通过当前正在解决的问题环境,或许能知道该如何处理除数为0的情况。但如果这是一个意料之外的值,你也不清楚该如何处理,那就要抛出异常,而不是顺着原来的路径继续执行下去。

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

举一个抛出异常的简单例子。对千对象引用t,传给你的时候可能尚未被初始化。 用这个对象引用调用其方法之前,会先对引用进行检查。可以创建一个代表错误信息的对象, 并且将它从当前环境中“抛出“,这样就把错误信息传播到了”更大”的环境中。 一个异常,看起来像这样:


if(t == null)
    throw new NullPointerException();

这就抛出了异常,千是在当前环境下就不必再为这个问题操心了,它将在别的地方得到处理。

**异常最重要的方面之一就是如果发生问题,它们将不允许程序沿浩其正常的路径继续走下去。**在C和C++这样的语言中,这可真是个问题,尤其是C,它没有任何办法可以强制程序在出 现问题时停止在某条路径上运行下去,因此我们有可能会较长时间地忽略了间题,从而陷人了完全不恰当的状态中。异常允许我们(如果没有其他手段)强制程序停止运行,并告诉我们出 现了什么问题,或者(理想状态下)强制程序处理间题,并返回到稳定状态。

异常参数

所有标准异常类都有两个构造器:

  • 一个是默认构造器;

  • 另一个是接受字符串作为参数,以便能把相关信息放人异常对象的构造器

throw new NullPointerException("t = null");

关键字throw将产生许多有趣的结果。在使用new创建了异常对象之后,此对象的引用将传给throw。

尽管返回的异常对象其类型通常与方法设计的返回类型不同,但从效果上看,它就像
是从方法”返回" 的。可以简单地把异常处理看成一种不同的返回机制,当然若过分强调这种类比的话,就会有麻烦了。另外还能用抛出异常的方式从当前的作用域退出。在这两种情况下,将会返回一个异常对象,然后退出方法或作用域。

异常返回的“ 地点” 与普通方法调用返回的“ 地点“ 完全不同。(异常将在一个恰当的异常处理程序中得到解决,它的位置可能离异常披抛出的地方很远,也可能会跨越方法调用栈的许多层次。)

此外,能够抛出任意类型的Tbrowable对象,它是异常类型的根类。

通常, 对千不同类型的
错误,要抛出相应的异常。错误信息可以保存在异常对象内部或者用异常类的名称来暗示。上一层环境通过这些信息来决定如何处理异常。(通常,异常对象中仅有的信息就是异常类型,除此之外不包含任何有意义的内容。)

捕获异常

要明白异常是如何被捕获的,必须首先理解监控区域(guarded region) 的概念。它是一段
可能产生异常的代码,并且后面跟若处理这些异常的代码。

try块

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

try {
// Code that might generate exceptions
}

异常处理程序

当然,抛出的异常必须在某处得到处理。这个“地点” 就是异常处理程序,而且针对每个要
捕获的异常,得准备相应的处理程序。异常处理程序紧跟在try块之后,以关键字catch表示:


try {
// Code that might generate exceptions
} catch(Type1 id1)|{
// Handle exceptions of Type1
} catch(Type2 id2) {
// Handle exceptions of Type2
} catch(Type3 id3) {
// Handle exceptions of Type3
}
// etc...

每个catch子句(异常处理程序)看起来就像是接收一个且仅接收一个特殊类型的参数的方法。

异常处理程序必须紧跟在try块之后。当异常被抛出时,异常处理机制将负责搜寻参数与异 常类型相匹配的第一个处理程序。然后进人catch子句执行,此时认为异常得到了处理。一且 catchh语句结束,则处理程序的查找过程结束。注意,只有匹配的catch子句才能得到执行,这与 switch语句不同,switch语句蒂要在每一个case后面跟一个break,以避免执行后续的case子句。

终止与恢复

异常处理理论上有两种基本模型。Java支持终止模型(它是Java和C++所支持的模型)。在这种模型中,将假设错误非常关键,以至千程序无法返回到异常发生的地方继续执行。一旦异常被抛出,就表明错误已无法挽回,也不能回来继续执行。

还有一种称为恢复模型。意思是异常处理程序的工作是修正错误,然后重新尝试调用出问题
的方法。

创建自定义异常

要自己定义异常类,必须从已有的异常类继承,最好是选择意思相近的异常类继承(不过这样的异常并不容易找)。建立新的异常类型最简单的方法就是让编译器为你产生默认构造器,所以这几乎不用写多少代码:

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("Caught it!");
        }
    }


}

编译器创建了默认构造器,它将自动调用基类的默认构造器。

本例中不会得到像Simple­
Exception(String)这样的构造器,这种构造器也不实用。你将看到,对异常来说,最重要的部分就是类名,所以本例中建立的异常类在大多数情况下已经够用了。

可以为异常类定义一个接受字符串参数的构造器:

class MyException extends Exception{

    public MyException() {}
    public MyException(String msg) { super(msg); }
}


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类声明(-Exception即从此类继承)printStackTrace()方法。就像从输出中看到的,它将打印”从方法调用处直到异常抛出处”的方法调用序列。这里,信息被发送到了System.out,并自动地被捕获和显示在输出中。但是,如果调用默认版本:


e.printStackTraceO:

异常与记录日志


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.getLoggerO方法创建了一个String参数相关联的Logger对象(通常与错误相
关的包名和类名)这个Logger对象会将其输出发送到Sysiem.err。

向Logger写入的最简单方式就是直接调用与日志记录消息的级别相关联的方法,这里使用的是severe()。 为了产生日志记录消息, 我们欲获取异常抛出处的栈轨迹, 但是printStackTrace()不会默认地产生字符串。为了获 取字符串, 我们需要使用重载的printStack仆ace()方法, 它接受一个java.io.PrintWriter对象作为参数.如果我们将一个java.io.StringWriter对象传递给这个 Print Writer的构造器, 那么通过调用toString()方法,就可以将输出抽取为一个String。

更常见的情形是 我们需要捕获和记录其他人编写的异常, 因此我们必须在异常处理程序中生成日志消息:


public class LoggingExceptions2 {

    private static Logger logger =
            Logger.getLogger("LoggingExceptions2");
    static void logException(Exception e) {
        StringWriter trace = new StringWriter();
        e.printStackTrace(new PrintWriter(trace));
        logger.severe(trace.toString());
    }
    public static void main(String[] args) {
        try {
            throw new NullPointerException();
        } catch(NullPointerException e) {
            logException(e);
        }
    }



}

还可以更进一步自定义异常, 比如加人额外的构造器和成员

class MyException2 extends Exception{

    private int x;
    public MyException2() {}
    public MyException2(String msg) { super(msg); }
    public MyException2(String msg, int x) {
        super(msg);
        this.x = x;
    }
    public int val() { return x; }
    public String getMessage() {
        return "Detail Message: "+ x + " "+ super.getMessage();
    }
}


public class ExtraFeatures {

    public static void f() throws MyException2 {
        print("Throwing MyException2 from f()");
        throw new MyException2();
    }
    public static void g() throws MyException2 {
        print("Throwing MyException2 from g()");
        throw new MyException2("Originated in g()");
    }
    public static void h() throws MyException2 {
        print("Throwing MyException2 from h()");
        throw new MyException2("Originated in h()", 47);
    }
    public static void main(String[] args) {
        try {
            f();
        } catch(MyException2 e) {
            e.printStackTrace(System.out);
        }
        try {
            g();
        } catch(MyException2 e) {
            e.printStackTrace(System.out);
        }
        try {
            h();
        } catch(MyException2 e) {
            e.printStackTrace(System.out);
            System.out.println("e.val() = " + e.val());
        }
    }


}

新的异常添加了字段x以及设定x值的构造器和读取数据的方法。此外,还覆盖了Throwable.
getMessage()方法,以产生更详细的信息。对千异常类来说,getMessage()方法有点类似 toStrlngO方法。

捕获所有异常

可以只写一个异常处理程序来捕获所有类型的异常。通过捕获异常类型的基类Exception,就可以做到这一点(事实上还有其他的基类,但Exception是同编程活动相关的基类):

catch(Exception e) {
        System.out.println("Caught an exception");
}

这将捕获所有异常,所以最好把它放在处理程序列表的末尾,以防它抢在其他处理程序之前先把异常捕获了。因为Exception是与编程有关的所有异常类的基类,所以它不会含有太多具体的信息

此外,也可以使用Throwable从其基类Object(也是所有类的基类)继承的方法。对千异常 来说,getClass()也许是个很好用的方法,它将返回一个表示此对象类型的对象。然后可以使用 getName()方法查询这个Class对象包含包信息的名称,或者使用只产生类名称的getSimple Name()方法。

public class ExceptionMethods {

    public static void main(String[] args) {
        try {
            throw new Exception("My Exception");
        } catch(Exception e) {
            print("Caught Exception");
            print("getMessage():" + e.getMessage());
            print("getLocalizedMessage():" +
                    e.getLocalizedMessage());
            print("toString():" + e);
            print("printStackTrace():");
            e.printStackTrace(System.out);
        }
    }


}

栈轨迹

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

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(ste.getMethodName());
        }
    }
    static void g() { f(); }
    static void h() { g(); }
    public static void main(String[] args) {
        f();
        System.out.println("--------------------------------");
        g();
        System.out.println("--------------------------------");
        h();
    }
}

重新抛出异常

有时希望把刚捕获的异常重新抛出,尤其是在使用Exception捕获所有异常的时候.

既然已经得到了对当前异常对象的引用,可以直接把它重新抛出:

catch(Exception e) {
System.out.println("An exception was thrown");
throw e;
}

重抛异常会把异常抛给上级环境中的异常处理程序,同一个try后续catch子句将被忽略。此外,异常对象的所有信息都得以保持,所以高一级环境中捕获此异常的处理程序可以从这个异常对象中得到所有信息。

如果只是把当前异常对象重新抛出,那么printStackTraceO方法显示的将是原来异常抛出点的调用栈信息,而并非重新抛出点的信息。要想更新这个信息,可以调用ftlllnStackTraceO方法,这将返回一个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);
        }
    }

}


调用filllnStackTraceO的那一行就成了异常的新发生地了。

有可能在捕获异常之后抛出另一种异常。这么做的话,得到的效果类似于使用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(
                        "Caught 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()一无所知.

异常链

常常会想要在捕获一个异常后抛出另一个异常,并且希望把原始异常的信息保存下来,这被称为异常链。

在JDKl.4以前,程序员必须自己编写代码来保存原始异常的信息。现在所有 Throwable的子类在构造器中都可以接受一个-cause(因由)对象作为参数。这个cause就用来表示原始异常,这样通过把原始异常传递给新的异常,使得即使在当前位置创建并抛出了新的异常,也能通过这个异常链追踪到异常最初发生的位置.

有趣的是,在Throwable的子类中,只有三种基本的异常类提供了带cause参数的构造器。它们是Error(用千Java虚拟机报告系统错误)Exception以及RuntimeException。

class DynamicFieldsException extends Exception{
}


public class DynamicFields {

    private Object[][] fields;
    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();
    }
    private int hasField(String id) {
        for(int i = 0; i < fields.length; i++)
            if(id.equals(fields[i][0]))
                return i;
        return -1;
    }
    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;
// Recursive call with expanded fields:
        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 dfe =
                    new DynamicFieldsException();
            dfe.initCause(new NullPointerException());
            throw dfe;
        }
        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);
        print(df);
        try {
            df.setField("d", "A value for d");
            df.setField("number", 47);
            df.setField("number2", 48);
            print(df);
            df.setField("d", "A new value for d");
            df.setField("number3", 11);
            print("df: " + df);
            print("df.getField(\"d\") : " + df.getField("d"));
            Object field = df.setField("d", null); // Exception
        } catch(NoSuchFieldException e) {
            e.printStackTrace(System.out);
        } catch(DynamicFieldsException e) {
            e.printStackTrace(System.out);
        }
    }
}



每个DynamicFields对象都含有一个数组,其元素是“成对的对象”。第一个对象表示字段标识符(一个字符串),第二个表示字段值,值的类型可以是除基本类型外的任意类型。 当创建.对象的时候, 要合理估计一下需要多少字段。 当调用setField()方法的时候, 它将试图通过标识匝可 修改已有字段值, 否则就建一个新的字段,并把值放入。 如果空间不够了, 将建立一个更长的数组, 并把原来数组的元素复制进去。 如果你试图为字段设置一个空值,将抛出一个 DynamicFieldsException异常, 它是通过使用initCauseO方法把NullPointerExcepti叨对象插入而 建立的。

Java标准异常

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

RuntimeException

if(t == null)
    throw new NullPointerException();

如果必须对传递给方法的每个引用都检查其是否为null(因为无法确定调用者是否传入了非法引用),这听起来着实吓人。幸运的是,这不必由你亲自来做,它属千Java的标准运行时检测的一部分。如果对null引用进行调用,Java会自动抛出NullPointerException异常,所以上述代码是多余的,尽管你也许想要执行其他的检查以确保NullPointerException不会出现。

属千运行时异常的类型有很多,它们会自动被Java虚拟机抛出,所以不必在异常说明中把它们列出来。这些异常都是从RuntimeException类继承而来,所以既体现了继承的优点,使用起来也很方便。这构成了一组具有相同特征和行为的异常类型。并且,也不再需要在异常说明 中声明方法将抛出RuntimeException类型的异常(或者任何从RuntimeException继承的异常),它们也被称为“不受检查异常"。这种异常属千错误,将被自动捕获,就不用你亲自动手了。要是自己去检查RuntimeException的话,代码就显得太混乱了。不过尽管通常不用捕获RuntiJneException异常,但还是可以在代码中抛出RuntiJneException类型的异常。

如果不捕获这种类型的异常会发生什么事呢?因为编译器没有在这个问题上对异常说明进行强制检查,RuntimeException类型的异常也许会穿越所有的执行路径直达main()方法,而不会被捕获。要明白到底发生了什么,可以试试下面的例子:

public class NeverCaught {

    static void f() {
        throw new RuntimeException("From f()");
    }
    static void g() {
        f();
    }
    public static void main(String[] args) {
        g();
    }



}

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

请务必记住:只能在代码中忽赂RuntimeException(及其子类)类型的异常,其他类型异常的处理都是由编译器强制实施的。究其原因,Runt血eException代表的是编程错误:

  • 无法预料的错误。比如从你控制范围之外传递进来的null引用。

  • 作为程序员,应该在代码中进行检查的错误。

值得注意的是:不应把Java的异常处理机制当成是单一用述的工具。是的,它被设计用来处理一些烦人的运行时错误,这些错误往往是由代码控制能力之外的因素导致的1然而,它对千发现某些编译器无法检测到的编程错误,也是非常重要的。

使用finally进行清理

对千一些代码,可能会希望无论t盯块中的异常是否抛出,它们都能得到执行。这通常适用 千内存回收之外的情况(因为回收由垃圾回收器完成)。为了达到这个效果,可以在异常处理程序后面加上finally子句

try {
// The guarded region: Dangerous activities
// that might throw A, B, or C
} catch(A a1) {
// Handler for situation A
} catch(B b1) {
// Handler for situation B
} catch(C c1) {
// Handler for situation C
} finally {
// Activities that happen every time
}

为了证明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子句总能被执行。

这个程序也给了我们一些思路,当Java中的异常不允许我们回到异常抛出的地点时,那么
该如何应对呢?如果把try块放在循环里,就建立了一个“程序继续执行之前必须要达到"的条件。还可以加入一个static类型的计数器或者别的装置,使循环在放弃以前能尝试一定的次数。这将使程序的健壮性更上一个台阶。

finally用来做什么

对千没有垃圾回收和析构函数自动调用机制e的语言来说,finally非常重要。它能使程序员保证:无论try块里发生了什么,内存总能得到释放。但Java有垃圾回收机制,所以内存释放不 再是问题。而且,Java也没有析构函数可供调用。那么,Jav诏:什么情况下才能用到finally呢?

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

public class Switch {

    private boolean state = false;
    public boolean read() { return state; }
    public void on() { state = true; print(this); }
    public void off() { state = false; print(this); }
    public String toString() { return state ? "on" : "off"; }


}

public class OnOffException1 extends Exception{
}

public class OnOffException2 extends Exception{
}

public class OnOffSwitch {

    private static Switch sw = new Switch();
    public static void f()
            throws OnOffException1,OnOffException2 {}
    public static void main(String[] args) {
        try {
            sw.on();
// Code that can throw exceptions...
            f();
            sw.off();
        } catch(OnOffException1 e) {
            System.out.println("OnOffException1");
            sw.off();
        } catch(OnOffException2 e) {
            System.out.println("OnOffException2");
            sw.off();
        }
    }
}


程序的目的是要确保main()结束的时候开关必须是关闭的,所以在每个tr炸九和异常处理程序的末尾都加入了对sw.off()方法的调用。 但也可能有这种情况:异常被抛出, 但没被处理程序捕获,这时sw.off()就得不到调用。 但是有了finally, 只要把t可块中的清理代码移放在一处即可:

public class WithFinally {

    static Switch sw = new Switch();
    public static void main(String[] args) {
        try {
            sw.on();
// Code that can throw exceptions...
            OnOffSwitch.f();
        } catch(OnOffException1 e) {
            System.out.println("OnOffException1");
        } catch(OnOffException2 e) {
            System.out.println("OnOffException2");
        } finally {
            sw.off();
        }
    }


}


甚至在异常没有被当前的异常处理程序捕获的情况下, 异常处理机制也会在跳到更高一层的异常处理程序之前,执行finally子句:


public class FourException extends Exception{
}

public class AlwaysFinally {

    public static void main(String[] args) {
        print("Entering first try block");
        try {
            print("Entering second try block");
            try {
                throw new FourException();
            } finally {
                print("finally in 2nd try block");
            }
        } catch(FourException e) {
            System.out.println(
                    "Caught FourException in 1st try block");
        } finally {
            System.out.println("finally in 1st try block");
        }
    }


}


在return中使用finally

因为finally子句总是会执行的,所以在一个方法中,可以从多个点返回,并且可以保证重要的清理工作仍旧会执行:

public class MultipleReturns {

    public static void f(int i) {
        print("Initialization that requires cleanup");
        try {
            print("Point 1");
            if(i == 1) return;
            print("Point 2");
            if(i == 2) return;
            print("Point 3");
            if(i == 3) return;
            print("End");
            return;
        } finally {
            print("Performing cleanup");
        }
    }
    public static void main(String[] args) {
        for(int i = 1; i <= 4; i++)
            f(i);
    }
}

从输出中可以看出,在ranally类内部,从何处返回无关紧要。

缺憾:异常丢失

遗憾的是,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) {
        try {
            LostMessage lm = new LostMessage();
            try {
                lm.f();
            } finally {
                lm.dispose();
            }
        } catch(Exception e) {
            System.out.println(e);
        }
    }


}

从输出中可以看到, VerylmportantException不见了, 它被finally子旬里的 HoHum­Exception所取代。 这是相当严重的缺陷, 因为异常可能会以一种比前面例子所示更微妙和难以 察觉的方式完全丢失。

一种更加简单的丢失异常的方式是从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{
}


public class PopFoul extends Foul{
}

class Strike extends BaseballException{
}

public interface Storm {

    public void event() throws RainedOut;
    public void rainHard() throws RainedOut;


}

public class RainedOut extends StormException{
}


public class StormException extends Exception{
}

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() {} // Throws no checked exceptions



}


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 methods must conform to base class:
//! void walk() throws PopFoul {} //Compile error
// Interface CANNOT add exceptions to existing
// methods from the base class:
//! public void event() throws RainedOut {}
// If the method doesn’t already 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() {}
    // Overridden 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 {
// What happens if you upcast?
            Inning i = new StormyInning();
            i.atBat();
// You must catch the exceptions from the
// base-class version of the 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");
        }
    }



}






常限制对构造器不起作用。你会发现StormyInning的构造器可以抛出任何异常,而不必
理会基类构造器所抛出的异常。然而,因为基类构造器必须以这样或那样的方式被调用(这里lilll 默认构造器将自动被调用),派生类构造器的异常说明必须包含基类构造器的异常说明。

派生类构造器不能捕获基类构造器抛出的异常。Stormy Inning. walk()不能通过编译的原因是因为:它抛出了异常,而Inning.walk()并没有声明此异常。如果编译器允许这么做的话,就可以在调用Inning.walk()的时候不用做异常处理了,而且当把它替换成Inning的派生类的对象时,这个方法就有可能会抛出异常,千是程序就失灵了。通过强制派生类遵守基类方法的异常说明,对象的可替换性得到了保证。

覆盖后的event()方法表明,派生类方法可以不抛出任何异常,即使它是基类所定义的异常。同样这是因为,假使基类的方法会抛出异常,这样做也不会破坏已有的程序,所以也没有问题。类似的情况出现在atBatO身上,它抛出的是PopFoul,这个异常是继承自“会被基类的atBat()抛出的Foul。这样,如果你写的代码是同Inning打交道,井且调用了它的atBat()的话,那么肯定能捕获Foul。而PopFoul是由Foul派生出来的,因此异常处理程序也能捕获PopFoul。

最后一个值得注意的地方是main()。这里可以看到,如果处理的刚好是StormyInning对象的话,编译器只会强制要求你捕获这个类所抛出的异常。但是如果将它向上转型成基类型,那么编译器就会(正确地)要求你捕获基类的异常。所有这些限制都是为了能产生更为强壮的异常处理代码.

发布了189 篇原创文章 · 获赞 58 · 访问量 18万+

猜你喜欢

转载自blog.csdn.net/Coder_py/article/details/103833039