Thinking in Java 第六章 类再生
有两个实现类再生的方法:
第一个最简单:在新类里简单地创建原有类的对象。我们把这种方法叫作“合成”,因为新类由现有 类的对象合并而成。我们只是简单地重复利用代码的功能,而不是采用它的形式。
第二种方法则显得稍微有些技巧。它创建一个新类,将其作为现有类的一个“类型”。我们可以 原样采取现有类的形式,并在其中加入新代码,同时不会对现有的类产生影响。这种魔术般 的行为叫作“继承”(Inheritance),涉及的大多数工作都是由编译器完成的。
示例:
第一种情况所涉及的内容
class Soap { private String s; Soap() { System.out.println("Soap()"); s = new String("Constructed"); } public String toString() { return s; } } public class Bath { private String // 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,使所有字段在使用之前都获得正确的初始化。
第二种继承的方法:
用于合成的语法是非常简单且直观的。但为了进行继承,必须采用一种全然不同的形式。需要继承的 时候,我们会说:“这个新类和那个旧类差不多。”为了在代码里表面这一观念,需要给出类名。但 在类主体的开始花括号之前,需要放置一个关键字extends,在后面跟随“基础类”的名字。若采取这 种做法,就可自动获得基础类的所有数据成员以及方法。
示例:
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 用于对字串进行“过载”处理。
进行继承时,我们并不限于只能使用基础类的方法。亦可在衍生出来的类里加入自己的新方法。这时 采取的做法与在普通类里添加其他任何方法是完全一样的:只需简单地定义它即可。extends关键字提 醒我们准备将新方法加入基础类的接口里,对其进行“扩展”。foam()便是这种做法的一个产物。
基础类的初始化:
基础类子对象应该正确地初始化,而且只有一种方法能保证这一点:在构建器中执行初始化,通 过调用基础类构建器,后者有足够的能力和权限来执行对基础类的初始化。在衍生类的构建器中, Java 会自动插入对基础类构建器的调用。
下面这个例子向大家展示了对这种三级继承的应用:
class Art { Art() { System.out.println("Art constructor"); } } class Drawing extends Art { Drawing() { System.out.println("Drawing constructor"); } } public class Cartoon extends Drawing { Cartoon() { System.out.println("Cartoon constructor"); } public static void main(String[] args) { Cartoon x = new Cartoon(); } }
该程序的输出显示了自动调用:
Art constructor
Drawing constructor
Cartoon constructor
如果构造器内含有变量应该调用super来完成对基础类的调用,
如下例子:
class Game { Game(int i) { System.out.println("Game constructor"); } } class BoardGame extends Game { BoardGame(int i) { super(i); System.out.println("BoardGame constructor"); } } public class Chess extends BoardGame { Chess() { super(11); System.out.println("Chess constructor"); } public static void main(String[] args) { Chess x = new Chess(); } }
这同时也会防止衍生类构建器捕获来自一个基础类的任何违例事件。显然,这有时会为我们造成不便。