Java编程思想第4版[中文版](PDF格式)-第45部分
按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!
在自己的类中定义它们。如下所示(若执行该程序时有麻烦,请参见第3 章3。1。2 小节“赋值”):
//: SprinklerSystem。java
// position for code reuse
package c06;
class WaterSource {
private String s;
WaterSource() {
System。out。println(〃WaterSource()〃);
s = new String(〃Constructed〃);
}
public String toString() { return s; }
}
public class SprinklerSystem {
private String valve1; valve2; valve3; valve4;
WaterSource source;
int i;
float f;
void print() {
System。out。println(〃valve1 = 〃 + valve1);
System。out。println(〃valve2 = 〃 + valve2);
System。out。println(〃valve3 = 〃 + valve3);
System。out。println(〃valve4 = 〃 + valve4);
System。out。println(〃i = 〃 + i);
System。out。println(〃f = 〃 + f);
System。out。println(〃source = 〃 + source);
139
…………………………………………………………Page 141……………………………………………………………
}
public static void main(String'' args) {
SprinklerSystem x = new SprinklerSystem();
x。print();
}
} ///:~
WaterSource 内定义的一个方法是比较特别的:toString()。大家不久就会知道,每种非基本类型的对象都
有一个 toString()方法。若编译器本来希望一个String,但却获得某个这样的对象,就会调用这个方法。所
以在下面这个表达式中:
System。out。println(〃source = 〃 + source) ;
编译器会发现我们试图向一个WaterSource 添加一个String 对象(〃source =〃)。这对它来说是不可接受
的,因为我们只能将一个字串“添加”到另一个字串,所以它会说:“我要调用toString(),把source 转
换成字串!”经这样处理后,它就能编译两个字串,并将结果字串传递给一个System。out。println()。每次
随同自己创建的一个类允许这种行为的时候,都只需要写一个 toString()方法。
如果不深究,可能会草率地认为编译器会为上述代码中的每个句柄都自动构造对象(由于Java 的安全和谨慎
的形象)。例如,可能以为它会为WaterSource 调用默认构建器,以便初始化 source。打印语句的输出事实
上是:
valve1 = null
valve2 = null
valve3 = null
valve4 = null
i = 0
f = 0。0
source = null
在类内作为字段使用的基本数据会初始化成零,就象第 2 章指出的那样。但对象句柄会初始化成null 。而且
假若试图为它们中的任何一个调用方法,就会产生一次“违例”。这种结果实际是相当好的(而且很有
用),我们可在不丢弃一次违例的前提下,仍然把它们打印出来。
编译器并不只是为每个句柄创建一个默认对象,因为那样会在许多情况下招致不必要的开销。如希望句柄得
到初始化,可在下面这些地方进行:
(1) 在对象定义的时候。这意味着它们在构建器调用之前肯定能得到初始化。
(2) 在那个类的构建器中。
(3) 紧靠在要求实际使用那个对象之前。这样做可减少不必要的开销——假如对象并不需要创建的话。
下面向大家展示了所有这三种方法:
//: Bath。java
// Constructor initialization with position
class Soap {
private String s;
Soap() {
System。out。println(〃Soap()〃);
s = new String(〃Constructed〃);
}
public String toString() { return s ; }
}
public class Bath {
private String
140
…………………………………………………………Page 142……………………………………………………………
// Initializing at point of definition:
s1 = new String(〃Happy〃);
s2 = 〃Happy〃;
s3; s4;
Soap castille;
int i;
float toy;
Bath() {
System。out。println(〃Inside Bath()〃);
s3 = new String(〃Joy〃);
i = 47;
toy = 3。14f;
castille = new Soap();
}
void print() {
// Delayed initialization:
if(s4 == null)
s4 = new String(〃Joy〃);
System。out。println(〃s1 = 〃 + s1);
System。out。println(〃s2 = 〃 + s2);
System。out。println(〃s3 = 〃 + s3);
System。out。println(〃s4 = 〃 + s4);
System。out。println(〃i = 〃 + i);
System。out。println(〃toy = 〃 + toy);
System。out。println(〃castille = 〃 + castille);
}
public static void main(String'' args) {
Bath b = new Bath();
b。print();
}
} ///:~
请注意在Bath 构建器中,在所有初始化开始之前执行了一个语句。如果不在定义时进行初始化,仍然不能保
证能在将一条消息发给一个对象句柄之前会执行任何初始化——除非出现不可避免的运行期违例。
下面是该程序的输出:
Inside Bath()
Soap()
s1 = Happy
s2 = Happy
s3 = Joy
s4 = Joy
i = 47
toy = 3。14
castille = Constructed
调用print()时,它会填充 s4,使所有字段在使用之前都获得正确的初始化。
6。2 继承的语法
继承与Java (以及其他OOP 语言)非常紧密地结合在一起。我们早在第 1 章就为大家引入了继承的概念,并
在那章之后到本章之前的各章里不时用到,因为一些特殊的场合要求必须使用继承。除此以外,创建一个类
时肯定会进行继承,因为若非如此,会从Java 的标准根类 Object 中继承。
141
…………………………………………………………Page 143……………………………………………………………
用于合成的语法是非常简单且直观的。但为了进行继承,必须采用一种全然不同的形式。需要继承的时候,
我们会说:“这个新类和那个旧类差不多。”为了在代码里表面这一观念,需要给出类名。但在类主体的起
始花括号之前,需要放置一个关键字extends,在后面跟随“基础类”的名字。若采取这种做法,就可自动
获得基础类的所有数据成员以及方法。下面是一个例子:
//: Detergent。java
// Inheritance syntax & properties
class Cleanser {
private String s = new String(〃Cleanser〃);
public void append(String a) { s += a; }
public void dilute() { append(〃 dilute()〃); }
public void apply() { append(〃 apply()〃); }
public void scrub() { append(〃 scrub()〃); }
public void print() { System。out。println(s); }
public static void main(String'' args) {
Cleanser x = new Cleanser();
x。dilute(); x。apply(); x。scrub();
x。print();
}
}
public class Detergent extends Cleanser {
// Change a method:
public void scrub() {
append(〃 Detergent。scrub()〃);
super。scrub(); // Call base…class version
}
// Add methods to the interface:
public void foam() { append(〃 foam()〃); }
// Test the new class:
public static void main(String'' args) {
Detergent x = new Detergent();
x。dilute();
x。apply();
x。scrub();
x。foam();
x。print();
System。out。println(〃Testing base class:〃);
Cleanser。main(args);
}
} ///:~
这个例子向大家展示了大量特性。首先,在Cleanser append()方法里,字串同一个 s 连接起来。这是用
“+=”运算符实现的。同“+”一样,“+=”被Java 用于对字串进行“过载”处理。
其次,无论 Cleanser 还是Detergent 都包含了一个main()方法。我们可为自己的每个类都创建一个
main()。通常建议大家象这样进行编写代码,使自己的测试代码能够封装到类内。即便在程序中含有数量众
多的类,但对于在命令行请求的public 类,只有main()才会得到调用。所以在这种情况下,当我们使用
“java Detergent”的时候,调用的是Degergent。main()——即使Cleanser 并非一个public 类。采用这种
将main()置入每个类的做法,可方便地为每个类都进行单元测试。而且在完成测试以后,毋需将main()删
去;可把它保留下来,用于以后的测试。
在这里,大家可看到Deteregent。main()对 Cleanser。main()的调用是明确进行的。
142
…………………………………………………………Page 144……………………………………………………………
需要着重强调的是Cleanser 中的所有类都是public 属性。请记住,倘若省略所有访问指示符,则成员默认
为“友好的”。这样一来,就只允许对包成员进行访问。在这个包内,任何人都可使用那些没有访问指示符
的方法。例如,Detergent 将不会遇到任何麻烦。然而,假设来自另外某个包的类准备继承Cleanser ,它就
只能访问那些public 成员。所以在计划继承的时候,一个比较好的规则是将所有字段都设为private,并将
所有方法都设为public (protected 成员也允许衍生出来的类访问它;以后还会深入探讨这一问题)。当
然,在一些特殊的场合,我们仍然必须作出一些调整,但这并不是一个好的做法。
注意Cleanser 在它的接口中含有一系列方法:append(),dilute(),apply(),scrub()以及print()。由于
Detergent 是从Cleanser 衍生出来的(通过 extends 关键字),所以它会自动获得接口内的所有这些方法—
—即使我们在 Detergent 里并未看到对它们的明确定义。这样一来,就可将继承想象成“对接口的重复利
用”或者“接口的再生”(以后的实施细节可以自由设置,但那并非我们强调的重点)。
正如在 scrub()里看到的那样,可以获得在基础类里定义的一个方法,并对其进行修改。在这种情况下,我
们通常想在新版本里调用来自基础类的方法。但在 scrub()里,不可只是简单地发出对scrub()的调用。那样
便造成了递归调用,我们不愿看到这一情况。为解决这个问题,Java 提供了一个 super 关键字,它引用当前
类已从中继承的一个“超类”(Superclass)。所以表达式super。scrub()调用的是方法 scrub()的基础类版
本。
进行继承时,我们并不限于只能使用基础类的方法。亦可在衍生出来的类里加入自己的新方法。这时采取的
做法与在普通类里添加其他任何方法是完全一样的:只需简单地定义它即可。extends 关键字提醒我们准备
将新方法加入基础类的接口里,对其进行“扩展”。foam()便是这种做法的一个产物。
在Detergent。main()里,我们可看到对于Detergent 对象,可调用Cleanser 以及Detergent 内所有可用的
方法(如foam())。
6。2。1 初始化基础类
由于这儿涉及到两个类——基础类及衍生类,而不再是以前的一个,所以在想象衍生类的结果对象时,可能
会产生一些迷惑。从外部看,似乎新类拥有与基础类相同的接口,而且可包含一些额外的方法和字段。但继
承并非仅仅简单地复制基础类的接口了事。创建衍生类的一个对象时,它在其中包含了基础类的一个“子对
象”。这个子对象就象我们根据基础类本身创建了它的一个对象。从外部看,基础类的子对象已封装到衍生
类的对象里了。
当然,基础类子对象应该正确地初始化,而且只有一种方法能保证这一点:在构建器中执行初始化,通过调
用基础类构建器,后者有足够的能力和权限来执行对基础类的初始化。在衍生类的构建器中,Java 会自动插
入对基础类构建器的调用。下面这个例子向大家展示了对这种三级继承的应用:
//: Cartoon。java
// Constructor calls during inheritance
class Art {
Art() {
System。out。println(〃Art constructor〃);