友情提示:如果本网页打开太慢或显示不完整,请尝试鼠标右键“刷新”本网页!
Java编程思想第4版[中文版](PDF格式)-第48部分
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部! 如果本书没有阅读完,想下次继续接着阅读,可使用上方 "收藏到我的浏览器" 功能 和 "加入书签" 功能!
口,我们可以同时获得抽象类以及接口的好处。所以假如想创建的基础类没有任何方法定义或者成员变量,
那么无论如何都愿意使用接口,而不要选择抽象类。事实上,如果事先知道某种东西会成为基础类,那么第
一个选择就是把它变成一个接口。只有在必须使用方法定义或者成员变量的时候,才应考虑采用抽象类。
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 属性。它们不能是“空白 final”,但可初始化成非常数表达
式。例如:
//: RandVals。java
// Initializing interface fields with
// non…constant initializers
import java。util。*;
public interface RandVals {
int rint = (int)(Math。random() * 10);
long rlong = (long)(Math。random() * 10);
float rfloat = (float)(Math。random() * 10);
double rdouble = Math。random() * 10;
} ///:~
由于字段是 static 的,所以它们会在首次装载类之后、以及首次访问任何字段之前获得初始化。下面是一个
简单的测试:
//: TestRandVals。java
public class TestRandVals {
public static void main(String'' args) {
System。out。println(RandVals。rint);
System。out。println(RandVals。rlong);
System。out。println(RandVals。rfloat);
System。out。println(RandVals。rdouble);
}
} ///:~
当然,字段并不是接口的一部分,而是保存于那个接口的 static存储区域中。
178
…………………………………………………………Page 180……………………………………………………………
7。6 内部类
在Java 1。1 中,可将一个类定义置入另一个类定义中。这就叫作“内部类”。内部类对我们非常有用,因为
利用它可对那些逻辑上相互联系的类进行分组,并可控制一个类在另一个类里的“可见性”。然而,我们必
须认识到内部类与以前讲述的“合成”方法存在着根本的区别。
通常,对内部类的需要并不是特别明显的,至少不会立即感觉到自己需要使用内部类。在本章的末尾,介绍
完内部类的所有语法之后,大家会发现一个特别的例子。通过它应该可以清晰地认识到内部类的好处。
创建内部类的过程是平淡无奇的:将类定义置入一个用于封装它的类内部(若执行这个程序遇到麻烦,请参
见第3 章的3。1。2 小节“赋值”):
//: Parcel1。java
// Creating inner classes
package c07。parcel1;
public class Parcel1 {
class Contents {
private int i = 11;
public int value() { return i; }
}
class Destination {
private String label;
Destination(String whereTo) {
label = whereTo;
}
String readLabel() { return label; }
}
// Using inner classes looks just like
// using any other class; within Parcel1:
public void ship(String dest) {
Contents c = new Contents();
Destination d = new Destination(dest);
}
public static void main(String'' args) {
Parcel1 p = new Parcel1();
p。ship(〃Tanzania〃);
}
} ///:~
若在 ship()内部使用,内部类的使用看起来和其他任何类都没什么分别。在这里,唯一明显的区别就是它的
名字嵌套在 Parcel1 里面。但大家不久就会知道,这其实并非唯一的区别。
更典型的一种情况是,一个外部类拥有一个特殊的方法,它会返回指向一个内部类的句柄。就象下面这样:
//: Parcel2。java
// Returning a handle to an inner class
package c07。parcel2;
public class Parcel2 {
class Contents {
private int i = 11;
public int value() { return i; }
}
class Destination {
private String label;
179
…………………………………………………………Page 181……………………………………………………………
Destination(String whereTo) {
label = whereTo;
}
String readLabel() { return label; }
}
public Destination to(String s) {
return new Destination(s);
}
public Contents cont() {
return new Contents();
}
public void ship(String dest) {
Contents c = cont();
Destination d = to(dest);
}
public static void main(String'' args) {
Parcel2 p = new Parcel2();
p。ship(〃Tanzania〃);
Parcel2 q = new Parcel2();
// Defining handles to inner classes:
Parcel2。Contents c = q。cont();
Parcel2。Destination d = q。to(〃Borneo〃);
}
} ///:~
若想在除外部类非 static 方法内部之外的任何地方生成内部类的一个对象,必须将那个对象的类型设为“外
部类名。内部类名”,就象main()中展示的那样。
7。6。1 内部类和上溯造型
迄今为止,内部类看起来仍然没什么特别的地方。毕竟,用它实现隐藏显得有些大题小做。Java 已经有一个
非常优秀的隐藏机制——只允许类成为“友好的”(只在一个包内可见),而不是把它创建成一个内部类。
然而,当我们准备上溯造型到一个基础类(特别是到一个接口)的时候,内部类就开始发挥其关键作用(从
用于实现的对象生成一个接口句柄具有与上溯造型至一个基础类相同的效果)。这是由于内部类随后可完全
进入不可见或不可用状态——对任何人都将如此。所以我们可以非常方便地隐藏实施细节。我们得到的全部
回报就是一个基础类或者接口的句柄,而且甚至有可能不知道准确的类型。就象下面这样:
//: Parcel3。java
// Returning a handle to an inner class
package c07。parcel3;
abstract class Contents {
abstract public int value();
}
interface Destination {
String readLabel();
}
public class Parcel3 {
private class PContents extends Contents {
private int i = 11;
public int value() { return i; }
180
…………………………………………………………Page 182……………………………………………………………
}
protected class PDestination
implements Destination {
private String label;
private PDestination(String whereTo) {
label = whereTo;
}
public String readLabel() { return label; }
}
public Destination dest(String s) {
return new PDestination(s);
}
public Contents cont() {
return new PContents();
}
}
class Test {
public static void main(String'' args) {
Parcel3 p = new Parcel3();
Contents c = p。cont();
Destination d = p。dest(〃Tanzania〃);
// Illegal …can't access private class:
//! Parcel3。PContents c = p。new PContents();
}
} ///:~
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部!
温馨提示: 温看小说的同时发表评论,说出自己的看法和其它小伙伴们分享也不错哦!发表书评还可以获得积分和经验奖励,认真写原创书评 被采纳为精评可以获得大量金币、积分和经验奖励哦!