按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!
如果想创建新类型的一个对象,它就必须是已提供所有定义的一个类。尽管Hero 没有为 fight()明确地提供
一个定义,但定义是随同ActionCharacter 来的,所以这个定义会自动提供,我们可以创建Hero 的对象。
在类Adventure 中,我们可看到共有四个方法,它们将不同的接口和具体类作为自己的自变量使用。创建一
个Hero 对象后,它可以传递给这些方法中的任何一个。这意味着它们会依次上溯造型到每一个接口。由于接
口是用Java 设计的,所以这样做不会有任何问题,而且程序员不必对此加以任何特别的关注。
注意上述例子已向我们揭示了接口最关键的作用,也是使用接口最重要的一个原因:能上溯造型至多个基础
类。使用接口的第二个原因与使用抽象基础类的原因是一样的:防止客户程序员制作这个类的一个对象,以
175
…………………………………………………………Page 177……………………………………………………………
及规定它仅仅是一个接口。这样便带来了一个问题:到底应该使用一个接口还是一个抽象类呢?若使用接
口,我们可以同时获得抽象类以及接口的好处。所以假如想创建的基础类没有任何方法定义或者成员变量,
那么无论如何都愿意使用接口,而不要选择抽象类。事实上,如果事先知道某种东西会成为基础类,那么第
一个选择就是把它变成一个接口。只有在必须使用方法定义或者成员变量的时候,才应考虑采用抽象类。
7。5。2 通过继承扩展接口
利用继承技术,可方便地为一个接口添加新的方法声明,也可以将几个接口合并成一个新接口。在这两种情
况下,最终得到的都是一个新接口,如下例所示:
//: HorrorShow。java
// Extending an interface with inheritance
interface Monster {
void menace();
}
interface DangerousMonster extends Monster {
void destroy();
}
interface Lethal {
void kill();
}
class DragonZilla implements DangerousMonster {
public void menace() {}
public void destroy() {}
}
interface Vampire
extends DangerousMonster; Lethal {
void drinkBlood();
}
class HorrorShow {
static void u(Monster b) { b。menace(); }
static void v(DangerousMonster d) {
d。menace();
d。destroy();
}
public static void main(String'' args) {
DragonZilla if2 = new DragonZilla();
u(if2);
v(if2);
}
} ///:~
DangerousMonster 是对Monster 的一个简单的扩展,最终生成了一个新接口。这是在DragonZilla 里实现
的。
Vampire 的语法仅在继承接口时才可使用。通常,我们只能对单独一个类应用 extends (扩展)关键字。但由
于接口可能由多个其他接口构成,所以在构建一个新接口时,extends可能引用多个基础接口。正如大家看
到的那样,接口的名字只是简单地使用逗号分隔。
176
…………………………………………………………Page 178……………………………………………………………
7。5。3 常数分组
由于置入一个接口的所有字段都自动具有 static 和final 属性,所以接口是对常数值进行分组的一个好工
具,它具有与C 或C++的enum 非常相似的效果。如下例所示:
//: Months。java
// Using interfaces to create groups of constants
package c07;
public interface Months {
int
JANUARY = 1; FEBRUARY = 2; MARCH = 3;
APRIL = 4; MAY = 5; JUNE = 6; JULY = 7;
AUGUST = 8; SEPTEMBER = 9; OCTOBER = 10;
NOVEMBER = 11; DECEMBER = 12;
} ///:~
注意根据Java 命名规则,拥有固定标识符的 static final基本数据类型(亦即编译期常数)都全部采用大
写字母(用下划线分隔单个标识符里的多个单词)。
接口中的字段会自动具备public 属性,所以没必要专门指定。
现在,通过导入c07。*或c07。Months,我们可以从包的外部使用常数——就象对其他任何包进行的操作那
样。此外,也可以用类似Months。JANUARY 的表达式对值进行引用。当然,我们获得的只是一个 int,所以不
象C++的enum 那样拥有额外的类型安全性。但与将数字强行编码(硬编码)到自己的程序中相比,这种(常
用的)技术无疑已经是一个巨大的进步。我们通常把“硬编码”数字的行为称为“魔术数字”,它产生的代
码是非常难以维护的。
如确实不想放弃额外的类型安全性,可构建象下面这样的一个类(注释①):
//: Month2。java
// A more robust enumeration system
package c07;
public final class Month2 {
private String name;
private Month2(String nm) { name = nm; }
public String toString() { return name; }
public final static Month2
JAN = new Month2(〃January〃);
FEB = new Month2(〃February〃);
MAR = new Month2(〃March〃);
APR = new Month2(〃April〃);
MAY = new Month2(〃May〃);
JUN = new Month2(〃June〃);
JUL = new Month2(〃July〃);
AUG = new Month2(〃August〃);
SEP = new Month2(〃September〃);
OCT = new Month2(〃October〃);
NOV = new Month2(〃November〃);
DEC = new Month2(〃December〃);
public final static Month2'' month = {
JAN; JAN; FEB; MAR; APR; MAY; JUN;
JUL; AUG; SEP; OCT; NOV; DEC
};
public static void main(String'' args) {
177
…………………………………………………………Page 179……………………………………………………………
Month2 m = Month2。JAN;
System。out。println(m);
m = Month2。month'12';
System。out。println(m);
System。out。println(m == Month2。DEC);
System。out。println(m。equals(Month2。DEC));
}
} ///:~
①:是 Rich Hoffarth 的一封E…mail 触发了我这样编写程序的灵感。
这个类叫作 Month2,因为标准 Java 库里已经有一个Month。它是一个 final 类,并含有一个private 构建
器,所以没有人能从它继承,或制作它的一个实例。唯一的实例就是那些 final static对象,它们是在类本
身内部创建的,包括:JAN,FEB,MAR 等等。这些对象也在month 数组中使用,后者让我们能够按数字挑选
月份,而不是按名字(注意数组中提供了一个多余的JAN,使偏移量增加了 1,也使 December 确实成为 12
月)。在main()中,我们可注意到类型的安全性:m 是一个 Month2 对象,所以只能将其分配给Month2。在
前面的Months。java 例子中,只提供了 int值,所以本来想用来代表一个月份的 int 变量可能实际获得一个
整数值,那样做可能不十分安全。
这儿介绍的方法也允许我们交换使用==或者equals(),就象main()尾部展示的那样。
7。5。4 初始化接口中的字段
接口中定义的字段会自动具有 static 和final 属