JAVA编程思想学习 — 第九章 (违例差错控制)

1.违例自变量
    和 Java 的其他任何对象一样, 需要用 new 在内存堆里创建违例,并需调用一个构建器。在所有标准违例中,存在着两个构建器: 第一个是默认构建器,第二个则需使用一个字串自变量,使我们能在违例里置入相关信息
    if(t == null)
    throw new NullPointerException("t = null”);
    在这儿,关键字 throw 会象变戏法一样做出一系列不可思议的事情。 它首先执行 new 表达式,创建一个不在程序常规执行范围之内的对象。而且理所当然,会为那个对象调用构建器。随后,对象实际会从方法中返回—— 尽管对象的类型通常并不是方法设计为返回的类型。为深入理解违例控制,可将其想象成另一种返回机制.通过“掷”出一个违例,亦可从原来的作用域中退出。但是会先返回一个值,再退出方法或作用域。
    但是,与普通方法返回的相似性到此便全部结束了,因为我们返回的地方与从普通方法调用中返回的地方是迥然有异的(我们 结束于一个恰当的违例控制器,它距离违例“掷”出的地方可能相当遥远—— 在调用堆栈中要低上许多级)。
     对于Java 的违例控制机制,它的一 个好处就是允许我们在一个地方将精力集中在要解决的问题上,然后在另一个地方对待来自那个代码内部的错误


2.违例的捕获:try块
    若位于一个方法内部,并“掷”出一个违例(或在这个方法内部调用的另一个方法产生了违例),那个方法 就会在违例产生过程中退出。 若不想一个 throw 离开方法,可在那个方法内部设置一个特殊的代码块,用它捕获违例。这就叫作“ try 块”,因为要在这个地方“尝试”各种方法调用。 try 块属于一种普通的作用域,用一个 try 关键字开头:
    try {
    // 可能产生违例的代码
    }
    若用一种不支持违例控制的编程语言全面检查错误,必须用设置和错误检测代码将每个方法都包围起来——即便多次调用相同的方法。而在使用了违例控制技术后, 可将所有东西都置入一个try 块内,在同一地点捕获所有违例。这样便可极大简化我们的代码,并使其更易辨读,因为代码本身要达到的目标再也不会与繁复的错误检查混淆。




3.违例的捕获:违例控制器

    生成的违例必须在某个地方中止。这个“地方”便是违例控制器或者违例控制模块。而且针对想捕获的每种违例类型,都必须有一个相应的违例控制器。违例控制器紧接在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 从句—— 即违例控制器—— 都类似一个小型方法,它需要采用一个(而且只有一个)特定类型的自变量。可在控制器内部使用标识符( id1, id2 等等),就象一个普通的方法自变量那样。我们有时也根本不使用标识符,因为违例类型已提供了足够的信息,可有效处理违例。 但即使不用,标识符也必须就位控制器必须“紧接”在 try 块后面。 若“掷”出一个违例,违例控制机制就会搜寻自变量与违例类型相符的
第一个控制器。随后,它会进入那个 catch 从句,并认为违例已得到控制(一旦 catch 从句结束,对控制器的搜索也会停止)。只有相符的 catch 从句才会得到执行;它与 switch 语句不同,后者在每个 case 后都需要一个 break 命令,防止误执行其他语句。
     在 try 块内部,请注意大量不同的方法调用可能生成相同的违例,但只需要一个控制器。


4.违例的捕获:违例规范
     Java 提供了一种特殊的语法格式(并强迫我们采用),以便礼貌地告诉客户程序员该方法会“掷”出什么违例,令对方方便地加以控制。这便是我们在这里要讲述的“违例规范”,它属于方法声明的一部分,位于自变量(参数)列表的后面。 违例规范采用了一个额外的关键字: throws ;后面跟随全部潜在的违例类型。因此,我们的方法定义看起来应象下面这个样子:
     void f() throws tooBig, tooSmall, divZero { //...
     它意味着不会从方法里“掷”出违例(除类型为RuntimeException 的违例以外,它可能从任何地方掷出 )。但不能完全依赖违例规范—— 假若方法造成了一个违例,但没有对其进行控制,编译器会侦测到这个情况,并告诉我们必须控制违例,或者指出应该从方法里“掷”出一个违例规范。通过坚持从顶部到底部排列违例规范, Java 可在编译期保证违例的正确性
     我们可创建一个控制器,令其捕获所有类型的违例。 具体的做法是捕获基础类违例类型Exception (也存在 其他类型的基础违例,但 Exception 是适用于几乎所有编程活动的基础)。如下所示: 
catch(Exception e) {
    System.out.println("caught an exception");
    }
这段代码能捕获任何违例,所以在实际使用时最好将其置于控制器列表的末尾,防止跟随在后面的任何特殊违例控制器失效。



5.标准的java违例:RuntimeException 的特殊情况
    有一些检查属于 Java 进行的标准运行期检查的一部分。Java 会自动产生一个 NullPointerException 违例。 这个类别里含有一系列违例类型。它们全部由 Java 自动生成,毋需我们亲自动手把它们包含到自己的违例规范里。最方便的是, 通过将它们置入单独一个名为 RuntimeException 的基础类下面,它们全部组合到一起。这是一个很好的继承例子:它建立了一系列具有某种共通性的类型,都具有某些共通的特征与行为。此外,我们没必要专门写一个违例规范,指出一个方法可能会“掷”出一个 RuntimeException,因为已经假定可能出现那种情况。由于它们用于指出编程中的错误,所以几乎永远不必专门捕获一个“运行期违例” ——RuntimeException—— 它在默认情况下会自动得到处理。


6.创建自己的违例
     并不一定非要使用 Java 违例。因为经常都需要创建自己的违例,以便指出自己的库可能生 成的一个特殊错误—— 但创建 Java 分级结构的时候,这个错误是无法预知的。为创建自己的违例类,必须从一个现有的违例类型继承—— 最好在含义上与新违例近似。继承一个违例相当简单: 
class MyException extends Exception {
        public MyException() {}
        public MyException(String msg) {
        super(msg);
        }
    }
  这里的关键是“ extends Exception”,它的意思是: 除包括一个 Exception 的全部含义以外,还有更多的含义。增加的代码数量非常少—— 实际只添加了两个构建器,对 MyException 的创建方式进行了定义。请记住,假如我们不明确调用一个基础类构建器,编译器会自动调用基础类默认构建器。在第二个构建器中,通过使用 super 关键字,明确调用了带有一个 String 参数的基础类构建器



7.用finally清除
     无论一个违例是否在 try 块中发生,我们经常都想执行一些特定的代码对一些特定的操作,经常都会遇到这种情况,但在恢复内存时一般都不需要(因为垃圾收集器会自动照料一切)。为达到这个目的, 可在所有违例控制器的末尾使用一个 finally 从句,所以完整的违例控制小节象下面这个样子: 
 try {
    // 要保卫的区域:
    // 可能“掷”出 A,B,或 C 的危险情况
    } catch (A a1) {
    // 控制器 A
    } catch (B b1) {
    // 控制器 B
    } catch (C c1) {
    // 控制器 C
    } finally {
    // 每次都会发生的情况
    }
8.构建器
    为违例编写代码时,我们经常要解决的一个问题是:“一旦产生违例,会正确地进行清除吗?”大多数时候都会非常安全,但在构建器中却是一个大问题。 构建器将对象置于一个安全的起始状态,但它可能执行一些操作—— 如打开一个文件。除非用户完成对象的使用,并调用一个特殊的清除方法,否则那些操作不会得到正确的清除。若从一个构建器内部“掷”出一个违例,这些清除行为也可能不会正确地发生。所有这些都意
味着在编写构建器时,我们必须特别加以留意。
    由于前面刚学了 finally,所以大家可能认为它是一种合适的方案。但事情并没有这么简单,因为 finally每次都会执行清除代码—— 即使我们在清除方法运行之前不想执行清除代码。因此,假如真的用 finally 进行清除,必须在构建器正常结束时设置某种形式的标志。而且只要设置了标志,就不要执行 finally 块内的任何东西。由于这种做法并不完美(需要将一个地方的代码同另一个地方的结合起来),所以除非特别需要,否则一般不要尝试在 finally 中进行这种形式的清除。


9.违例匹配
     “掷”出一个违例后,违例控制系统会按当初编写的顺序搜索“最接近”的控制器。一旦找到相符的控制 器, 就认为违例已得到控制,不再进行更多的搜索工作。 在违例和它的控制器之间,并不需要非常精确的匹配。一个衍生类对象可与基础类的一个控制器相配,如下例所示: 
//: Human.java
// Catching Exception Hierarchies
class Annoyance extends Exception {}
class Sneeze extends Annoyance {}
public class Human {
  public static void main(String[] args) {
    try {
      throw new Sneeze();
    } catch(Sneeze s) {
      System.out.println("Caught Sneeze");
    } catch(Annoyance a) {
      System.out.println("Caught Annoyance");
    }
  }
} ///:~
    
Sneeze 违例会被相符的第一个 catch 从句捕获。当然,这只是第一个。然而,假如我们删除第一个 catch 从句:
try {
        throw new Sneeze();
    } catch(Annoyance a) {
        System.out.println("Caught Annoyance");
    }

  那么剩下的 catch 从句依然能够工作,因为它捕获的是 Sneeze 的基础类。换言之, catch(Annoyance e)能捕获一个 Annoyance 以及从它衍生的任何类。这一点非常重要,因为一旦我们决定为一个方法添加更多的违例,而且它们都是从相同的基础类继承的,那么客户程序员的代码就不需要更改。至少能够假定它们捕获的是基础类。




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

叶孤心丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值