友情提示:如果本网页打开太慢或显示不完整,请尝试鼠标右键“刷新”本网页!
Java编程思想第4版[中文版](PDF格式)-第69部分
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部! 如果本书没有阅读完,想下次继续接着阅读,可使用上方 "收藏到我的浏览器" 功能 和 "加入书签" 功能!
e。val() = 47
由于违例不过是另一种形式的对象,所以可以继续这个进程,进一步增强违例类的能力。但要注意,对使用
自己这个包的客户程序员来说,他们可能错过所有这些增强。因为他们可能只是简单地寻找准备生成的违
例,除此以外不做任何事情——这是大多数 Java 库违例的标准用法。若出现这种情况,有可能创建一个新违
例类型,其中几乎不包含任何代码:
//: SimpleException。java
class SimpleException extends Exception {
} ///:~
它要依赖编译器来创建默认构建器(会自动调用基础类的默认构建器)。当然,在这种情况下,我们不会得
到一个SimpleException(String)构建器,但它实际上也不会经常用到。
9。5 违例的限制
覆盖一个方法时,只能产生已在方法的基础类版本中定义的违例。这是一个重要的限制,因为它意味着与基
础类协同工作的代码也会自动应用于从基础类衍生的任何对象(当然,这属于基本的 OOP 概念),其中包括
违例。
下面这个例子演示了强加在违例身上的限制类型(在编译期):
//: StormyInning。java
// Overridden methods may throw only the
// exceptions specified in their base…class
// versions; or exceptions derived from the
// base…class exceptions。
class BaseballException extends Exception {}
class Foul extends BaseballException {}
class Strike extends BaseballException {}
abstract class Inning {
Inning() throws BaseballException {}
void event () throws BaseballException {
// Doesn't actually have to throw anything
}
abstract void atBat() throws Strike; Foul;
void walk() {} // Throws nothing
}
class StormException extends Exception {}
class RainedOut extends StormException {}
class PopFoul extends Foul {}
interface Storm {
void event() throws RainedOut;
void rainHard() throws RainedOut;
}
public class StormyInning extends Inning
implements Storm {
// OK to add new exceptions for constructors;
274
…………………………………………………………Page 276……………………………………………………………
// but you must deal with the base constructor
// exceptions:
StormyInning() throws RainedOut;
BaseballException {}
StormyInning(String s) throws Foul;
BaseballException {}
// Regular methods must conform to base class:
//! void walk() throws PopFoul {} //pile error
// Interface CANNOT add exceptions to existing
// methods from the base class:
//! public void event() throws RainedOut {}
// If the method doesn't already exist in the
// base class; the exception is OK:
public void rainHard() throws RainedOut {}
// You can choose to not throw any exceptions;
// even if base version does:
public void event() {}
// Overridden methods can throw
// inherited exceptions:
void atBat() throws PopFoul {}
public static void main(String'' args) {
try {
StormyInning si = new StormyInning();
si。atBat();
} catch(PopFoul e) {
} catch(RainedOut e) {
} catch(BaseballException e) {}
// Strike not thrown in derived version。
try {
// What happens if you upcast?
Inning i = new StormyInning();
i。atBat();
// You must catch the exceptions from the
// base…class version of the method:
} catch(Strike e) {
} catch(Foul e) {
} catch(RainedOut e) {
} catch(BaseballException e) {}
}
} ///:~
在 Inning 中,可以看到无论构建器还是event()方法都指出自己会“掷”出一个违例,但它们实际上没有那
样做。这是合法的,因为它允许我们强迫用户捕获可能在覆盖过的event()版本里添加的任何违例。同样的
道理也适用于abstract 方法,就象在 atBat()里展示的那样。
“interface Storm”非常有趣,因为它包含了在 Ining 中定义的一个方法——event(),以及不是在其中
定义的一个方法。这两个方法都会“掷”出一个新的违例类型:RainedOut。当执行到“StormyInning
extends”和“implements Storm”的时候,可以看到Storm 中的event()方法不能改变 Inning中的event()
的违例接口。同样地,这种设计是十分合理的;否则的话,当我们操作基础类时,便根本无法知道自己捕获
的是否正确的东西。当然,假如interface 中定义的一个方法不在基础类里,比如rainHard(),它产生违例
时就没什么问题。
对违例的限制并不适用于构建器。在StormyInning 中,我们可看到一个构建器能够“掷”出它希望的任何东
西,无论基础类构建器“掷”出什么。然而,由于必须坚持按某种方式调用基础类构建器(在这里,会自动
275
…………………………………………………………Page 277……………………………………………………………
调用默认构建器),所以衍生类构建器必须在自己的违例规范中声明所有基础类构建器违例。
StormyInning。walk()不会编译的原因是它“掷”出了一个违例,而 Inning。walk()却不会“掷”出。若允许
这种情况发生,就可让自己的代码调用 Inning。walk(),而且它不必控制任何违例。但在以后替换从 Inning
衍生的一个类的对象时,违例就会“掷”出,造成代码执行的中断。通过强迫衍生类方法遵守基础类方法的
违例规范,对象的替换可保持连贯性。
覆盖过的event()方法向我们显示出一个方法的衍生类版本可以不产生任何违例——即便基础类版本要产生
违例。同样地,这样做是必要的,因为它不会中断那些已假定基础类版本会产生违例的代码。差不多的道理
亦适用于atBat(),它会“掷”出PopFoul——从 Foul 衍生出来的一个违例,而Foul 违例是由 atBat()的基
础类版本产生的。这样一来,假如有人在自己的代码里操作 Inning,同时调用了atBat(),就必须捕获Foul
违例。由于 PopFoul 是从Foul 衍生的,所以违例控制器(模块)也会捕获PopFoul。
最后一个有趣的地方在 main()内部。在这个地方,假如我们明确操作一个StormyInning 对象,编译器就会
强迫我们只捕获特定于那个类的违例。但假如我们上溯造型到基础类型,编译器就会强迫我们捕获针对基础
类的违例。通过所有这些限制,违例控制代码的“健壮”程度获得了大幅度改善(注释③)。
③:ANSI/ISO C++施加了类似的限制,要求衍生方法违例与基础类方法掷出的违例相同,或者从后者衍生。
在这种情况下,C++实际上能够在编译期间检查违例规范。
我们必须认识到这一点:尽管违例规范是由编译器在继承期间强行遵守的,但违例规范并不属于方法类型的
一部分,后者仅包括了方法名以及自变量类型。因此,我们不可在违例规范的基础上覆盖方法。除此以外,
尽管违例规范存在于一个方法的基础类版本中,但并不表示它必须在方法的衍生类版本中存在。这与方法的
“继承”颇有不同(进行继承时,基础类中的方法也必须在衍生类中存在)。换言之,用于一个特定方法的
“违例规范接口”可能在继承和覆盖时变得更“窄”,但它不会变得更“宽”——这与继承时的类接口规则
是正好相反的。
9。6 用 finally 清除
无论一个违例是否在try 块中发生,我们经常都想执行一些特定的代码。对一些特定的操作,经常都会遇到
这种情况,但在恢复内存时一般都不需要(因为垃圾收集器会自动照料一切)。为达到这个目的,可在所有
违例控制器的末尾使用一个finally 从句(注释④)。所以完整的违例控制小节象下面这个样子:
try {
// 要保卫的区域:
// 可能“掷”出A;B;或C 的危险情况
} catch (A a1) {
// 控制器 A
} catch (B b1) {
// 控制器 B
} catch (C c1) {
// 控制器 C
} finally {
// 每次都会发生的情况
}
④:C++违例控制未提供finally 从句,因为它依赖构建器来达到这种清除效果。
为演示 finally 从句,请试验下面这个程序:
//: FinallyWorks。java
// The finally clause is always executed
public class FinallyWorks {
static int count = 0;
public static void main(String'' args) {
276
…………………………………………………………Page 278……………………………………………………………
while(true) {
try {
// post…increment is zero first time:
if(count++ == 0)
throw new Exception();
System。out。println(〃No exception〃);
} catch(Exception e) {
System。out。println(〃Exception thrown〃);
} finally {
System。out。println(〃in finally clause〃);
if(count == 2) break; // out of 〃while〃
}
}
}
} ///:~
通过该程序,我们亦可知道如何应付Java 违例(类似 C++的违例)不允许我们恢复至违例产生地方的这一事
实。若将自己的try 块置入一个循环内,就可建立一个条件,它必须在继续程序之前满足。亦可添加一个
static计数器或者另一些设备,允许循环在放弃以前尝试数种不同的方法。这样一来,我们的程序可以变得
更加“健壮”。
输出如下:
Exception thrown
in finally clause
No exception
in finally clause
无论是否“掷”出一个违例,finally 从句都会执行。
9。6。1 用 finally 做什么
在没有“垃圾收集”以及“自动调用破坏器”机制的一种语言中(注释⑤),finally 显得特别重要,因为
程序员可用它担保内存的正确释放——无论在 try 块内部发生了什么状况。但Java 提供了垃圾收集机制,所
以内存的释放几乎绝对不会成为问题。另外,它也没有构建器可供调用。既然如此,Java 里何时才会用到
finally 呢?
⑤:“破坏器”(Destructor)是“构建器”(Constructor)的反义词。它代表一个特殊的函数,一旦某个
对象失去用处,通常就会调用它。我们肯定知道在哪里以及何时调用破坏器。C++提供了自动的破坏器调用机
制,但Delphi 的Object Pascal 版本 1及 2 却不具备这一能力(在这种语言中,破坏器的含义与用法都发生
了变化)。
除将内存设回原始状态以外,若要设置另一些东西,finally 就是必需的。例如,我们有时需要打开一个文
件或者建立一个网络连接,或者在屏幕上画一些东西,甚至设置外部世界的一个开关,等等。如下例所示:
//: OnOffSwitch。java
// Why use finally?
class Switch {
boolean state = false;
boolean read() { return state; }
void on() { state = true; }
void off() { state = false; }
}
277
…………………………………………………………Page 279……………………………………………………………
public class OnOffSwitch {
static Switch sw = new Switch();
public static void main(String'' args) {
try {
sw。on();
// Code that can throw exceptions。。。
sw。off();
} catch(NullPointerException e) {
System。out。println(〃NullPointerException〃);
sw。off();
} catch(IllegalArgumentException e) {
System。out。println(〃IOException〃);
sw。off();
}
}
} ///:~
这里的目标是保证main()完成时开关处于关闭状态,所以将 sw。off()置于 try 块以及每个违例控制器的末
尾。但产生的一个违例有可能不是在这里捕获的,这便会错过 sw。off()。然而,利用finally,我们可以将
来自 try 块的关闭代码只置于一个地方:
//: WithFinally。java
// Finally Guarantees cleanup
class Switch2 {
boolean state = false;
boolean read() { return state; }
void on() { state = true; }
void off() { state = false; }
}
public class WithFinally {
static Switch2 sw = new Switch2();
public static void main(String'' args) {
try {
sw。on();
// Code that can throw exceptions。。。
} catch(NullPointerException e) {
System。out。println(〃NullPointerException〃);
} catch(IllegalArgumentException e) {
System。out。println(〃IOException〃);
} finally {
sw。off();
}
}
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部!
温馨提示: 温看小说的同时发表评论,说出自己的看法和其它小伙伴们分享也不错哦!发表书评还可以获得积分和经验奖励,认真写原创书评 被采纳为精评可以获得大量金币、积分和经验奖励哦!