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

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

1. 概念

  • 异常往往能够降低错误处理代码的复杂度,如果不使用异常,那么就必须检查特定的错误,并在程序中的许多地方去处理它。而如果使用异常,那就不必在方法调用处进行检查,因为异常机制将保证能够捕获这个错误。并且,只需在一个地方处理错误,即所谓的异常处理程序中。这种方式不仅节省代码,而且把“描述在正常执行过程中做什么事”的代码和“出了问题怎么办”的代码相分离。

2. 基本异常

  • 异常情形是指阻止当前方法或作用域继续执行的问题。把异常情形与普通问题相区分很重要,所谓的普通问题是指,在当前环境下能得到足够的信息,总能处理这个错误。而对于异常情形,就不能继续下去了,因为在当前环境下无法获得必要的信息来解决问题。你所能做的就是从当前环境跳出,并且把问题提交给上一级环境。这就是抛出异常时所发生的事情。
  • 异常使得我们可以将每件事都当作一个事务来考虑,而异常可以看护着这些事务的底线“…事务的基本保障是我们所需的在分布式计算中的异常处理。事务是计算机中的合同法,如果出了什么问题,我们只需要放弃整个计算。”我们还可以将异常看作是一种内建的恢复系统,因为我们在程序中可以拥有各种不同的恢复点。如果程序的某部分失败了,异常将“恢复”到程序中某个已知的稳定点上。
  • 异常最重要的方面之一就是如果发生问题,他们将不允许程序沿着其正常的路径继续走下去。
  • 异常允许我们(如果没有其他手段)强制程序停止运行,并告诉我们出现了什么问题,或者(理想状态下)强制程序处理问题,并返回到稳定状态。
  • 尽管返回的异常对象其类型通常与方法设计的返回类型不同,但从效果上看,它就像是从方法“返回”的。可以简单地把异常处理看作一种不同的返回机制,当然若过分强调这种类比的话,就会有麻烦。另外很能用抛出异常的方式从当前的作用域退出。在这两种情况下,将会返回一个异常对象,然后退出方法或作用域。
  • 抛出异常与方法正常返回值的相似之处到此为止。因为异常返回的“地点”与普通方法调用返回的“地点”完全不同。(异常将在一个恰当的异常处理程序中得到解决,它的位置可能离异常被抛出的位置很远,也可能会跨越方法调用栈的许多层次。)

3. 捕获异常

  • 如果在方法内部抛出了异常(或者在方法内部调用的其他方法抛出了异常),这个方法将在抛出异常的过程中结束。要是不希望方法就此结束,可以在方法内设置一个特殊的块来捕获异常。因为在这个块里“尝试”各种(可能产生异常的)方法调用,所以称为try块。它是跟在try关键词之后的普通程序块。
  • 异常处理程序紧跟在try块之后,以关键词catch表示。
  • 每个catch子句(异常处理程序)看起来就像是接收一个且仅接收一个特殊类型的参数的方法。
  • 异常处理理论上有两种基本类型。Java支持终止模型。在这种模型中,将假设错误非常关键,以至于程序无法返回到异常发生的地方继续执行。一旦异常被抛出,就表明错误已无法挽回,也不能回来继续执行。
  • 另一种称为恢复模型。意思是异常处理程序的工作是修正错误,然后重新尝试调用出问题的方法,并认为第二次能成功。对于恢复模型,通常希望异常被处理之后能继续执行程序。
  • 虽然恢复模型开始显得很吸引人,但不是很实用。其中的主要原因可能是它所导致的耦合:恢复性的处理程序需要了解异常抛出的地点,这势必要包含依赖于抛出位置的非通用性代码。这增加了代码和维护的困难,对于异常可能会从许多地方抛出的大型程序来说,更是如此。

4. 创建自定义异常

  • getMessage()方法,以产生更详细的信息。对于异常类来说,getMessage()方法有点类似于toString()方法。

5. 异常说明

  • 在编译时被强制检查的异常称为被检查的异常。

6. 捕获所有异常

  • 通过捕获异常类型的基类Exception,可以只写一个异常处理程序来捕获所有类型的异常。
  • 使用fillInStackTrace(),有关原来异常发生点的信息会丢失,剩下的是与新的抛出点有关的信息。
  • 永远不要为清理前一个异常对象而担心,或者说为异常对象的清理而担心。它们都是用new在堆上创建的对象,所以垃圾回收器会自动把他们清理掉。
  • 常常会想要在捕获一个异常后抛出另一个异常,并希望把原始的异常的信息保存下来,这被称为异常链。
  • 在Throwable的子类中,只有三种基本的异常类提供了带cause参数的构造器。它们是Error(用于Java虚拟机报告系统错误)、Exception以及RuntimeException。如果要把其他类型的异常链接起来,应该使用initCause()方法而不是构造器。

7. 标准异常

  • Throwable这个Java类被用来表示任何可以作为异常被抛出的类。Throwable对象可分为两种类型(指从Throwable继承而得到的类型):Error用来表示编译时和系统错误(除特殊情况外,一般不用你担心);Exception是从可以被抛出的基本类型,在Java类库、用户方法以及运行时故障中都可能抛出Exception型异常。
  • 属于运行时异常的类型有很多,他们会自动被Java虚拟机抛出,这些异常都是从RuntimeException类继承而来。他们也被称为“不受检查异常”,这种异常属于错误,将被自动捕获。
  • 只能在代码中忽略RuntimeException(及其子类)类型的异常,其他类型异常的处理都是由编译器强制实施的。究其原因,RuntimeException代表的是编程错误:
  1. 无法预料的错误。比如从你控制范围之外传递进来的null引用
  2. 作为程序员,应该在代码中进行检查的错误。在一个地方发生的异常,常常会在另一个地方导致错误。

8. 使用finally进行清理

  • 无论异常是否被抛出,finally子句总能被执行。
  • finally非常重要,它能使程序员保证:无论try块发生了什么,内存总能得到释放。但Java有垃圾回收机制,所以内存释放不再是问题。当要把除内存之外的资源恢复到他们的初始状态时,就要用到finally子句。这种需要的清理的资源包括:已经打开的文件或网络连接等。
  • 因为finally子句总是会被执行的,所以在一个方法中,可以从多个点返回return,并且可以保证重要的清理工作仍旧会进行。在finally内部,从何处返回无关紧要。

9. 异常的限制

  • 当覆盖方法的时候,只能抛出在基类方法的异常说明里列出的那些异常。这个限制很有用,因为这意味着,当基类使用的代码应用到其派生类对象的时候,一样能够工作。(当然,这个面向对象的基本概念),异常也不例外。
  • 异常限制对构造器不起作用。你会发现派生类的构造器可以抛出任何异常,而不必理会基类构造器所抛出的异常。然而,因为基类构造器必须以这样或那样的方式被调用(这里默认构造器将自动被调用),派生类构造器的异常说明必须包含基类构造器的异常说明。
  • 派生类构造器不能捕获基类构造器抛出的异常。
  • 通过强制派生了遵守基类方法的异常说明,对象的可替换性得到了保证。
  • 如果处理的刚好是派生类对象的话,编译器只会强制要求你捕获这个类所抛出的异常。但是如果将它向上转型成基类型,那么编译器就会(正确地)要求你捕获基类的异常。所有这些限制都是为了能产生更为强壮的异常处理代码。
  • 异常说明本身并不属于方法类型的一部分,方法类型是有方法的名字与参数的类型组成的。因此,不能基于异常说明来重载方法。此外,一个出现在基类方法的异常说明中的异常,不一定会出现在派生类方法的异常说明里。这点同继承的规则明显不同,在继承里,基类的方法必须出现在派生类里,换句话说,在继承和覆盖的过程中,某个特定的方法的“异常说明的接口”不是变大了而是变小了,这恰好和类接口在继承时的情形相反。

10. 构造器

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

11. 异常匹配

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

12. 其他可选方式

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

13. 异常使用指南

  • 应该在以下情况下使用异常:
  1. 在恰当的级别处理问题。(在知道该如何处理的情况下才捕获异常。)
  2. 解决问题并且重新调用产生异常的方法。
  3. 进行少许修补,然后绕过异常发生的地方继续执行。
  4. 用别的数据进行计算,以代替方法预计会返回的值。
  5. 把当前运行环境下能做的事情尽量做完,然后把相同的异常重抛到更高层。
  6. 把当前运行环境下能做的事情尽量做完,然后把不同的异常跑到更高层。
  7. 终止程序。
  8. 进行简化。
  9. 让类库和程序更安全。
发布了57 篇原创文章 · 获赞 11 · 访问量 9881

猜你喜欢

转载自blog.csdn.net/qq_36160730/article/details/97039196