按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!
public class Foreign {
public static void main (String'' args) {
PackagedClass pc = new PackagedClass();
}
137
…………………………………………………………Page 139……………………………………………………………
} ///:~
解释编译器为什么会产生一个错误。将Foreign (外部)类作为c05 包的一部分改变了什么东西吗?
138
…………………………………………………………Page 140……………………………………………………………
第 6 章 类再生
“Java 引人注目的一项特性是代码的重复使用或者再生。但最具革命意义的是,除代码的复制和修改以外,
我们还能做多得多的其他事情。”
在象C 那样的程序化语言里,代码的重复使用早已可行,但效果不是特别显著。与Java 的其他地方一样,这
个方案解决的也是与类有关的问题。我们通过创建新类来重复使用代码,但却用不着重新创建,可以直接使
用别人已建好并调试好的现成类。
但这样做必须保证不会干扰原有的代码。在这一章里,我们将介绍两个达到这一目标的方法。第一个最简
单:在新类里简单地创建原有类的对象。我们把这种方法叫作“合成”,因为新类由现有类的对象合并而
成。我们只是简单地重复利用代码的功能,而不是采用它的形式。
第二种方法则显得稍微有些技巧。它创建一个新类,将其作为现有类的一个“类型”。我们可以原样采取现
有类的形式,并在其中加入新代码,同时不会对现有的类产生影响。这种魔术般的行为叫作“继承”
(Inheritance),涉及的大多数工作都是由编译器完成的。对于面向对象的程序设计,“继承”是最重要的
基础概念之一。它对我们下一章要讲述的内容会产生一些额外的影响。
对于合成与继承这两种方法,大多数语法和行为都是类似的(因为它们都要根据现有的类型生成新类型)。
在本章,我们将深入学习这些代码再生或者重复使用的机制。
6。1 合成的语法
就以前的学习情况来看,事实上已进行了多次“合成”操作。为进行合成,我们只需在新类里简单地置入对
象句柄即可。举个例子来说,假定需要在一个对象里容纳几个 String 对象、两种基本数据类型以及属于另一
个类的一个对象。对于非基本类型的对象来说,只需将句柄置于新类即可;而对于基本数据类型来说,则需
在自己的类中定义它们。如下所示(若执行该程序时有麻烦,请参见第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 = 〃 + castil