《Java编程思想》第十二章 通过异常处理错误

一世执手 提交于 2019-12-17 17:02:25

java的基本理念是“结构不佳的代码不能运行”
发现错误的理想实际时在编译阶段,也就是在你试图运行程序之前。然而,编译期间并不能找出所有的错误,余下的问题必须在运行期间解决,这就需要错误源能通过某种方式,把适当的信息传递给某个接收者-该接收者知道如何正确处理这个问题。
改进的错误恢复机制是提供代码健壮性的最强有力的方式。Java使用异常来提供一致性错误报告模型,使得构件能够与客户端代码可靠地沟通问题。

基本异常

异常情形是指组织当前方法或作用域继续执行下去的问题。把异常情形与普通问题向区分很重要,所谓的普通问题是指,在当前环境下能得到足够的信息,总能处理这个错误。而对于异常情形,就不能继续下去了,因为当前环境下无法获得必要的信息来解决问题。你所能做的就是从当前环境跳出,并且把问题提交给上一级环境。这就是抛出异常时所发生的事情。
当抛出异常后,有几件事情会随之发生。首先,同Java中其他对象的创建一样,将使用new在堆上创建异常对象,然后,当前的执行路劲被终止,并且从当前环境中弹出对异常对象的引用,此时,异常处理机制接管程序,并开始寻找一个恰当的地方来继续执行程序。这个恰当的地方就是异常处理程序,它的任务时将程序从错误状态中恢复,以使程序能要么换一种方式运行,要么继续运行下去。抛出异常,看起来像这样:

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

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

异常参数

我们总是用new在堆上创建异常对象,这也伴随着存储空间的分配和构造器的调用。所有标准异常类都有两个构造器:一个是默认构造器;另一个是接受字符串作为参数,以便能把相关信息放入异常对象的构造器:

throw new NullPointerException("t = null");

捕获异常

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

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
}

//etc...

每个catch语句看起来就像是接受一个且仅接受一个特殊类型的参数的方法。有时可能用不到标识符,但是标识符并不可以省略。
终止与恢复
异常处理理论上有两种基本模型。Java支持终止模型(它是Java和c++所支持的模型)。在这种模型中,将假设错误非常关键,以至于程序无法返回到异常放生的地方继续执行。一旦异常被抛出,就表明错误已经无法挽回,也不能回来继续执行。
另一种称为恢复模型。意思是异常处理程序的工作是修正错误,然后重新尝试调用出问题的方法,并认为第二次能成功。对于恢复模型,通常希望异常被处理之后能继续执行程序。如果想要用Java实现恢复的行为,那么在遇见错误时就不能抛出异常,而是调用正确方法来修正该错误。或者,把try块放在while循环里,这样就不断进入try块,直到得到满意的结果。

创建自定义异常

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

class SimpleException extends Exception{}

public classs InteritingExceptions{
     public void f() throw 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!");
        }
     }
}

output:

Throw SimpleException from f()
Caught it!

编译器创建了默认构造器,它将自动调用基类的默认构造器。本例的结果被打印到了控制台,但是,你也许想通过写入System.err而将错误发送给标准错误流。通常这比把错误信息输出到System.out要好,因为System.out也许会被重定向。

异常说明

程序库通常并不与源码一起发送异常。为了预防这样的问题,Java提供了相应的语法(并强制使用这个语法),使你能以礼貌的方式告知客户端程序员某个方法可能会抛出的异常类型,然后客户端程序员就可以进行相应的处理。这就是异常说明,它属于方法声明的一部分,紧跟在形式参数列表后面。
在编译时被强制检查的异常称为被检查的异常

捕获所有异常

可以只写一个异常处理程序来捕获所有类型的异常。通过捕获异常类型的基类Exception,就可以做到这一点:

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

这将捕获所有异常,所以最好把它放在处理程序列表的末尾。

栈轨迹

printStackTrace()方法所提供的信息可以通过getStackTrace()方法来直接访问,这个方法将返回一个由栈轨迹中的元素所构成的数组,其中而每一个元素都表示栈中的一帧。元素是栈顶元素,并且是调用序列中的最后一个方法调用。

重新抛出异常

有时希望把刚捕获的异常重新抛出,尤其是在使用Exception捕获所有异常的时候。既然已经得到了对当前异常对象的引用,可以直接把它重新抛出:

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

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

异常链

常常会想要在捕获一个异常后抛出另一个异常,并且希望把原始异常的信息保存下来,这被称为异常链。现在,所有Throwable的子类在构造器中都可以接受一个cause对象作为参数。这个cause就用来表示原始异常。
在Throwable的子类中,只有三种基本的异常类提供了带cause参数的构造器,它们是Error(用于Java虚拟机报告系统错误)、Exception以及RuntimeException。如果要把其他类型的异常链接起来,应该使用**initCause()**方法而不是构造器。

Java标准异常

Ttrowable这个Java类被用来表示任何可以作为异常被抛出的类。Throwable对象可分为两种类型(指从Throwable继承而得到的类型):Error用来表示编译时和系统错误;Exception时可以抛出的基本类型。

使用finally进行清理

对于一些代码,可能会希望无论try块中的异常是否抛出,它们都能得到执行。这通常适用于内存回收之外的情况。为了达到这种效果,可以在异常处理程序后面加上finally子句。无论异常是否被抛出,finally子句总能执行。
对于没有垃圾回收和析构函数自动调用机制的语言来说,finally非常重要,但是Java有垃圾回收期,也没有析构函数可供调用,那么,Java在什么情况下才用到finally呢?
当要把除内存之外的资源恢复到它们的初始状态时,就要用到finally子句。这种需要清理的资源包括:已经打开的文件或网络链接,在屏幕上画的图形,甚至可以时外部世界的某个开关。

缺憾:异常丢失

Java的异常实现也有瑕疵,用某些特殊的方式使用finally会导致异常被忽略。或者在finally中返回也会导致异常的丢失。

异常的限制

异常限制对构造器不起作用。派生类构造器的异常说明必须包含基类构造器的异常说明。
派生类构造器不能捕获基类构造器抛出的异常。

构造器

如果异常发生了,所有东西能被正确的清理吗?涉及构造器时,问题就出现了。构造器会把对象设置成安全的初始状态,但还会有别的动作,比如打开一个文件,这样的动作只有在对象使用完毕并且用户调用了特殊的清理方法后才能得以清理。如果在构造器内抛出异常,这些清理行为也许就不能正常运行了。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!