java编程思想读书笔记 第十二章 通过异常处理错误(下)

1.异常的限制

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

下面的例子是在编译时施加在异常上面的限制:

public class BaseBallException extends Exception {}
public class Foul extends BaseBallException{}
public class Strike extends BaseBallException{}
public abstract class Inning {
    public Inning() throws BaseBallException{}
    public void event() throws BaseBallException{}
    public abstract void addBat()throws Strike,Foul;
    public void walk() {}
}
public class StormException extends Exception{}
public class RainedOut extends StormException{}
public class PopFoul extends Foul{}
public interface Storm {
    public void event() throws RainedOut;
    public void rainHard() throws RainedOut;
}
public class StromMyInnerings extends Inning implements Storm{
    /**
     * @throws BaseBallException
     */
    public StromMyInnerings() throws RainedOut,BaseBallException {}
    public StromMyInnerings(String s)throws RainedOut,BaseBallException{}
    /**
     * @throws RainedOut
     */
    @Override
    public void rainHard() throws RainedOut {}
    /**
     * @throws Strike
     */
    @Override
    public void addBat() throws PopFoul {}
    public void event(){}
    public static void main(String[] args) {
        StromMyInnerings stromMyInnerings;
        try {
            stromMyInnerings = new StromMyInnerings();
            stromMyInnerings.addBat();
        } catch (RainedOut e) {
            System.out.println("RainedOut");
        } catch (PopFoul e) {
            System.out.println("PopFoul");
        } catch (BaseBallException e) {
            System.out.println("BaseBallException");
        }
        Inning inning;
        try {
            inning = new StromMyInnerings();
            inning.addBat();
        } catch (Strike e) {
            System.out.println("Strike");
        } catch (Foul e) {
            System.out.println("Foul");
        } catch (RainedOut e) {
            System.out.println("RainedOut");
        } catch (BaseBallException e) {
            System.out.println("BaseBallException");
        }
    }
}

在Inning类中,可以看到构造器和event()方法都声明将抛出异常,但实际上没有抛出。这种方式使你能强制用户去捕获可能在覆盖后的event()版本中增加的异常,所以它很合理。这对于抽象方法同样成立,如addBat()。

接口Storm值得注意,因为它包含了一个在Inning中定义的方法event()和一个不在Inning中定义的方法rainHard()。这两个方法都抛出新的异常RainedOut。如果StromMyInnerings类在扩展Inning类的同事又出现了Storm接口,那么Storm里的event()方法就不能改变Inning中的event()方法的异常接口。否则的话,在使用基类的时候就不能判断是否捕获了正常的异常。当然,如果接口里定义的方法不是来自于基类,比如rainHard(),那么此方法抛出什么样的异常都没有问题。

异常限制对构造器不起作用,你会发现StromMyInnerings的构造器可以抛出任何异常,而不必理会基类构造器所抛出的异常。派生类构造器不能捕获基类构造器抛出的异常。

尽管在继承过程中,编译器会对异常说明做强制要求,但异常说明本身并不属于方法类型的一部分,方法类型是由方法的名字与参数的类型组成的。因此,不能基于异常说明来重载方法。此外,一个出现在基类方法的异常,不一定会出现在派生类方法的异常说明里。换句话说,在继承和覆盖的过程中,某个特定方法的“异常说明的接口”不是变大了而是变小了–这恰好和类接口在继承时的情形相反。

2.构造器

如果一个类继承了某个类同时又实现了某个接口,他们有同样的接口方法,但都抛出了不同的捕获性异常,则该子类实现与重写该方法时,则方法声明处不能抛出任何捕获性异常了。如果调用的父类构造器抛出捕获性异常,则子类相应的构造器也只能抛出,不能在构造器里进行捕获。构造器抛出异常时正确的清理方式:比如在构造器中打开了一个文件,清理动作只有在对象使用完毕并且用户调用了特殊的清理方法之后才能得以清理,而不能直接在构造器里的finally块上关闭,因为finally块是不管是否有异常都会关闭,而构造器执行成功能外界需要这个文件流。但如果在文件成功打开后才抛出异常,则需要关闭文件,并向外界抛出异常信息:

public class InputFile {
    private BufferedReader in;
    public InputFile(String fname) throws Exception{
        try {
            in = new BufferedReader(new FileReader(fname));
        } catch (FileNotFoundException e) {
            System.out.println("could not open "+fname);
            throw e;
        }catch (Exception e) {
            try {
                in.close();
            } catch (IOException e1) {
                System.out.println("in.close() unsuccessful");
                throw e;
            }finally{
            }
        }
    }
    public String getLine(){
        String s;
        try {
            s=in.readLine();
        } catch (IOException e) {
            throw new RuntimeException("readLine() failed");
        }
        return s;
    }
    public void dispose(){
        try {
            in.close();
            System.out.println("dispose() successful");
        } catch (IOException e) {
            throw new RuntimeException("in.close() failed");
        }
}
public class CleanUp {
    public static void main(String[] args) {
        try {
            InputFile inputFile = new InputFile("D:/workspace/testjava/src/com/test/CleanUp.java");
            try {
                String s;
                int i = 1;
                while ((s = inputFile.getLine()) != null) {
                }
            } catch (Exception e) {
                System.out.println("caught Exception in main");
                e.printStackTrace(System.out);
            }finally{
                inputFile.dispose();
            }
        } catch (Exception e) {
            System.out.println("InputFile construction failed");
        }
     }
}

InputFile 的构造器接受字符串作为参数,该字符串表示所要打开的文件名。在try块中,会使用此文件名建立了FileReader对象。FileReader对象本身用处并不大,但可以用它来建立BufferedReader对象。注意,使用InputFile 的好处就是把两步操作合二为一。这种通用的清理习惯用法在构造器不抛出任何异常时也应该运用,其基本规则是:在创建需要清理的对象之后,立即进入一个try-finally语句块。

3.异常匹配

抛出异常的时候,异常处理系统会按照代码的书写顺序找出“最近”的处理程序。找到匹配的处理程序之后,它就认为异常将得到处理,然后就不再继续查找。

查找的时候并不要求抛出的异常处理程序所声明的异常完全匹配。派生类的对象也可以匹配其基类的处理程序,例子如下:

public class Annoyance extends Exception{}
public class Sneeze extends Annoyance{}
public class Human {
    public static void main(String[] args) {
        try {
            throw new Sneeze();
        } catch (Sneeze e) {
            System.out.println("catch Sneeze");
        }catch (Annoyance e) {
            System.out.println("catch Annoyance");
        }
        try {
            throw new Sneeze();
        }catch (Annoyance e) {
            System.out.println("catch Annoyance exception");
        }
    }
}

Sneeze异常会被第一个匹配的catch自居补货,也就是程序里的第一个。然而如果将这个catch字句删掉,只留下Annoyance的catch子句,该横向仍然能正常运行,因为这次补货的是Sneeze的基类。换句话说,catch(Annoyance e)会捕获Annoyance以及所有从它派生的异常。

如果把捕获基类的catch子句放在最前面,以此想把派生类的异常全给“屏蔽”掉,就像这样:

try {
            throw new Sneeze();

        }catch (Annoyance e) {
            //...
        } catch (Sneeze e) {
            //.....
        }

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

4.其他的可选方式

异常处理的一个重要的原则是“只有在你知道如何处理的情况下才捕获异常”。实际上,异常处理的一个重要目标就是把错误处理的代码同错误发生的地点相分离。这使你能在一段代码中专注于要完成的事情,至于如何处理错误,则放在另一段代码中。这样以来,主干代码就不会与错误处理逻辑混在一起,也更容易理解和维护。

“被检查的异常”可能使问题变得复杂,因为它们强制你在可能还没有准备好处理错误的时候被迫加上cacth子名,即使我们不知道如何处理的情况下,这就导致了异常的隐藏:

try{
   //… throw …
}catch(Exception e){//什么都不做
}

把“被检查的异常”转换为“不检查的异常”:当在一个普通方法里调用别的方法时,要考虑到“我不知道该怎样处理这个异常,但是也不能把它‘吞’了,或者只打印一些无用的消息”。JDK1.4的异常链提供了一种新的思路来解决这个问题,可以直接把“被检查的异常”包装进RuntimeException里面:

try{
   //… throw 检查异常…
}catch(IDontKnowWhatToDoWithThisCheckedException e){
   Throw new RuntimeException(e);
}

如果想把“被检查的异常”这种功能“屏蔽”掉的话,上面是一个好的办法。不用“吞”掉异常,也不必把它放到方法的异常声明里面,而异常链还能保证你不会丢失任何原始异常的信息。你还可以在“知道如何处理的地方”来处理它,也可以其他上层catch里通过 throw e.getCause(); 再次抛出原始的“被检查的异常”。

5.异常使用指南

应该在以下情况使用异常:

1)在恰当的级别处理问题。(在知道该如何处理的情况下才捕获异常。)

2)解决问题并且重新调用产生异常的方法。

3)进行少许修补,然后绕过异常产生的地方继续执行。

4)用别的数据进行计算,以代替方法预计会返回的值。

5)把当前运行环境下能做的事情尽量做完,然后把相同的异常重抛到更高层。

6)把当前运行环境下能做的事情尽量做完,然后把不同的异常抛到更高层。

7)终止程序。

8)进行简化。

9)让类库和程序更安全。

6.总结

异常时java程序设计不可分割的一部分,如果不了解 如何使用它们,那你只能完成很有限的工作。异常处理的有点之一就是它使得你可以在某处集中精力处理你要解决的问题,而在另一边处理你编写的这段代码中产生的错误。

时间: 2024-10-27 00:13:50

java编程思想读书笔记 第十二章 通过异常处理错误(下)的相关文章

《JAVA编程思想》学习笔记——第十二章 通过异常处理错误

Java的基本理念是 "结构不佳的代码不能运行" 发现错误的理想时机是在编译阶段,也就是在你试图运行程序之前.然而,编译期间并不能找出所有的错误,余下的问题必须在运行期间解决.这就需要错误源能通过某种方式,把适当的信息传递给某个接收者----该接收者将知道如何正确处理这个问题. 异常情形是指阻止当前方法或作用域继续执行的问题. 当抛出异常后,有几件事会随之发生.首先,同Java中其它对象的创建一样,将使用new在堆上创建异常对象.然后,当前的执行路径被终止,并且从当前环境中弹出对异常对

Java编程思想读书笔记_第三章

本章提到的关于==的部分,一个完整的实验如下: 1 class Test { 2 public static void main(String[] args) { 3 Integer i = new Integer(47); 4 Integer j = new Integer(47); 5 Integer i1 = 47; 6 Integer j1 = 47; 7 int i2 = new Integer(47); 8 int j2 = new Integer(47); 9 int i3 = 4

Java编程思想读书笔记_第7章

final关键字类似const: 1 import java.util.*; 2 3 public class FinalData { 4 static Random rand = new Random(47); 5 final int valueOne = 9; 6 final int i4 = rand.nextInt(20); 7 static final int INT_5 = rand.nextInt(20); 8 public static void main(String[] ar

Java编程思想读书笔记_第6章(访问权限)

四种访问权限: public private 包访问权限 protected 如果没有明确指定package,则属于默认包 1 package access.dessert; 2 3 public class Cookie { 4 public Cookie() { 5 System.out.println("Cookie()"); 6 } 7 8 void bite() { 9 System.out.println("bite"); 10 } 11 } 12 13

JAVA编程思想读书笔记(五)--多线程

接上篇JAVA编程思想读书笔记(四)--对象的克隆 No1: daemon Thread(守护线程) 参考http://blog.csdn.net/pony_maggie/article/details/42441895 daemon是相于user线程而言的,可以理解为一种运行在后台的服务线程,比如时钟处理线程.idle线程.垃圾回收线程等都是daemon线程. daemon线程有个特点就是"比较次要",程序中如果所有的user线程都结束了,那这个程序本身就结束了,不管daemon是否

Java编程思想读书笔记

声明:原创作品,转载时请注明文章来自SAP师太技术博客:www.cnblogs.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将追究法律责任!原文链接:http://www.cnblogs.com/jiangzhengjun/p/4290955.html 第一章对象导论... 1 第二章一切都是对象... 4 第三章操作符... 10 第四章流程控制... 12 第五章初始化与清理... 14 第六章访问权限控制... 15 第七章复用... 23 第八章多态... 2

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

第十二章  通过异常处理错误(中) 12.4 创建自定义异常 我们不必拘泥于Java中已有的异常类型,Java提供的异常体系不可能预见所有的错误,所以可以自己定义异常类来表示程序中可能会遇到的特定问题:要自己定义异常类,必须从已有的异常类继承,最好是选择意思相近的异常类继承,建立新的异常类最简单的方法就是让编译器为你产生默认构造器,所以这几乎不用写多少代码: class SimpleException extends Exception { } public class InheritingEx

java编程思想读书笔记三(11-21)

十一:持有对象 >持有对象实例 ●数组将数字与对象联系起来.它保存类型明确的对象,查询对象时,不需要对结果做类型转换.他可以是多维的. 可以保存基本的数据类型.但是,数组一旦生成,容量就不会在变. ●Collection保存单一的元素,而Map保存相关联的键值对.有了泛型,你就可以指定存放的对象类型,获取的时候,也不需要类型转换.各种Collection与Map都可以自动调整大小.容器不能持有基本类型.但是会自动包装. ●像数组一样,List也建立数字索引与对象的关联.因此,数组和List都是排

Java编程思想读书笔记之内部类

现在是够懒得了,放假的时候就想把这篇笔记写出来,一直拖到现在,最近在读<Java编程思想>,我想会做不止这一篇笔记,因为之前面试的时候总会问道一些内部类的问题,那这本书的笔记就从内部类开始说起. 一.为什么需要内部类 1.一个内部类的对象可以访问它的外围类的私有数据. 2.对于同一个包中的其他类来说,内部类能够隐藏起来. 3.使用内部类实现多重继承. 二.内部类重要提要 1.在方法内部或者方法内部的作用域的内部类 eg:方法内部的内部类 public interface InnerInterf