Java编程思想---第十二章 通过异常处理错误(下)

第十二章  通过异常处理错误(下)

12.6.3 异常链

 

  常常会想要在捕获一个异常后抛出另一个异常,并且希望把原始异常的信息保存下来,这被称为异常链。在JDK 1.4之前,程序员必须自己编写代码来保存原始异常的信息,现在所有Throwable的子类在构造器中都可以接受一个cause对象作为参数,这个cause就用来表示原始异常,这样通过把原始异常传递给新的异常,使得即使在当前位置创建并抛出新的异常,也能通过这个异常链追踪到最初发生的位置。

  在Throwable子类中,只有三种基本的异常类提供了带cause参数的构造器,分别是:Error,Exception,RuntimeException。

下面的例子能让我们在运行时动态地向DynamicFields对象添加字段:

 

package com.example.demo.exceptions;

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;

            }

        }

        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) {

            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);

        } catch (NoSuchFieldException e) {

            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 {

            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(\"d\") : " + df.getField("d"));

            Object field = df.setField("d", null);

        } catch (NoSuchFieldException e){

            e.printStackTrace(System.out);

        } catch (DynamicFieldsException e) {

            e.printStackTrace(System.out);

        }

    }

}
View Code

 

输出结果为:

null: null

null: null

null: null

 

d: A value for d

number: 47

number2: 48

 

df: d: A new value for d

number: 47

number2: 48

number3: 11

 

df.getField("d") : A new value for d

com.example.demo.exceptions.DynamicFieldsException

at com.example.demo.exceptions.DynamicFields.setField(DynamicFields.java:55)

at com.example.demo.exceptions.DynamicFields.main(DynamicFields.java:83)

Caused by: java.lang.NullPointerException

at com.example.demo.exceptions.DynamicFields.setField(DynamicFields.java:56)

... 1 more

 

 

12.7 Java标准异常

 

  Throwable这个Java类被用来表示任何可以作为异常被抛出的类,Throwable对象可分为两种类型,Error用来表示编译时和系统错误,Exception是可以被抛出的基本类型,在Java类库、用户方法以及运行时故障中都可能抛出Exception异常,所以Java程序员关心的基类型通常是Exception。

  异常的基本概念是用名称代表发生的问题,并且异常的名称应该可以望文知意,但异常并非全是在java.lang包中定义的,有些异常是用来支持其他像util、net、io这样的程序包,这些异常可以通过他们的完整名称或者从他们的父类中看出端倪,比如所有的输入输出异常都是从java.io.IOException继承而来的。

 

12.8 使用finally进行清理

 

  对于一些代码,可能会希望无论try块中的异常是否抛出,他们都能得到执行,这通常适用于内存回首之外的情况,为了达到这个效果,可以在异常处理程序后面加上finally子句,完整的异常处理程序看起来像这样:

 

try {
  //codes
} catch (Type id1) {
  //handle code of type1
} catch (Type id2) {
  //handle code of type2
} finally {
  //Activities that happen every time
}

 

  为了证明finally子句总能执行,可以试试下面的程序:

package com.example.demo.exceptions;

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;
            }
        }
    }
}

 

输出结果如下:

ThreeException

In finally clause

No exception

In finally clause

 

12.8.1 finally用来做什么

 

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

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

 

12.8.2 在return中使用finally

 

  因为finally子句总是会执行,所以在一个方法中,可以从多个点返回,并且可以保证重要的清理工作仍会执行。在finally内部,从何处返回无关紧要。

 

12.8.3 缺憾:异常丢失

 

  遗憾的是,Java的异常实现也有瑕疵。异常作为程序出错的标志,绝不应该被忽略,但它还是有可能被轻易忽略,用某些特殊的方式使用finally子句,就会发生这种情况:

package com.example.demo.exceptions;

 

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);

        }

    }

}
View Code

 

输出结果为:

A trivial exception

 

  从输出中可以看出,VeryimportantException不见了,它被finally子句中的HoHumException所取代,这是相当严重的缺陷。

 

12.9 异常的限制

 

  当覆盖方法的时候,只能抛出在基类方法的异常说明里列出的那些异常,这个限制很有用,这意味着当基类使用的代码应用到其派生类对象的时候一样能够工作,异常也不例外。

 

12.10 构造器

 

  有一点很重要,即你要时刻询问自己如果异常发生了,所有东西能被正确清理吗?尽管大多数情况下是非常安全的,但涉及构造器时,问题就出现了,构造器会把对象设置成安全的初始状态,但还会有别的动作,比如打开一个文件,这样的动作只有在对象使用完毕并且用户调用了特殊的清理方法之后才能得以清理,如果在构造器内抛出了异常,这些清理行为也许就不能正常工作了,这意味着在编写构造器时要格外细心。

  读者也许会认为使用finally就可以解决问题,但问题并非如此简单,因为finall会每次都执行清理代码,如果构造器在其执行过程中半途而废,也许该对象的某些部分还没有被成功创建,而这些部分在finally子句中却是要被清理的。

 

 

12.11 异常匹配

 

  抛出异常的时候,异常处理系统会按照代码的书写顺序找出最近的处理程序,找到匹配的处理程序之后他就认为异常将得到处理,然后就不在继续寻找。查找的时候并不要求抛出的异常同处理程序所声明的异常完全匹配,派生类的对象也可以匹配其基类的处理程序。

 

12.12 其他可选方式

 

  异常处理系统就像一个活门,使你能放弃程序的正常执行序列,当异常情形发生的时候正常的执行已变得不可能或者不需要了,这时就要用到这个活门。异常代表了当前方法不能继续执行的情形,开发异常处理系统的原因是,如果每个方法所有可能发生的错误都进行处理的话,任务就显得过于繁重了,程序员也不愿意这么做,结果常常是将错误忽略,应该注意到开发异常处理的初衷是为了方便程序员处理错误。

  异常处理的一个重要原则是只有在你知道如何处理的情况下才捕获异常。实际上,异常处理的一个重要目标就是把错误处理的代码同错误发生的地点相分离,这使你能在一段代码中专注于要完成的事情,置于如何处理错误则放在另一段代码中之完成。这样一来,主干代码就不会与错误处理逻辑混在一起,也更容易理解和维护,通过允许一个处理程序去处理多个出错点,异常处理还使得错误处理代码的数量趋于减少。

posted @ 2019-10-07 15:44  寓言i  阅读(184)  评论(0编辑  收藏  举报