(hasA)
进行合成只需在新类里简单地置入对象句柄即可。
//: SprinklerSystem.java // Composition 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); } public static void main(String[] args) { SprinklerSystem x = new SprinklerSystem(); x.print(); } } ///:~ /* Output: WaterSource() valve1 = null valve2 = null valve3 = null valve4 = null i = 0 f = 0.0 source = Constructed *///:
注意toString()方法,每种非基本类型的对象都有一个toString()方法。若编译器本来希望一个String,但却获得某个对象,就会调用这个方法。所以在下面这个表达式中:
System.out.println("source = " + source) ;
编译器会发现我们试图向一个WaterSource添加一个String对象(“source =”)。这对它来说是不可接受的,因为只能将一个字串“添加”到另一个字串,所以它会说:“我要调用toString(),把source转换成字串!”经这样处理后,它就能编译两个字串,并将结果字串传递给System.out.println()。
要在自己创建的类中允许上述这种行为,只需要写一个toString()方法即可。
注意对象句柄会初始化为null,调用其方法会抛出违例
子类继承父类(isA)
语法:访问控制符【修饰符】class 类名 extends 父类名{}
关键字:extends
例如: public class Students extends Person{}
//: 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); } } /* Output: Cleanser dilute() apply() Detergent.scrub() scrub() foam() Testing base class: Cleanser dilute() apply() scrub() ///:~
注意:
无论Cleanser还是Detergent都包含了一个main()方法。我们可为自己的每个类都创建一个main()。通常建议大家这样编写代码,使自己的测试代码能够封装到类中。即便在程序中含有数量众多的类,但对于在命令行请求的public类,只有main()才会得到调用。所以在这种情况下,当我们使用“java Detergent”的时候,调用的是Degergent.main()——即使Cleanser并非一个public类。采用这种将main()置入每个类的做法,可方便地为每个类都进行单元测试。而且在完成测试以后,毋需将main()删去;可把它保留下来,用于以后的测试。
使用Deteregent.main()调用主方法
基础类(父类)和衍生类(子类)。从外部看,新类似乎与基础类拥有相同的接口,可能还包含一些额外添加的方法和字段。但继承不是简单复制基础类的接口就完了。创建衍生类的一个对象时,它在其中包含了基础类的一个“子对象”。这个子对象就象我们根据基础类本身创建了它的一个对象。从外部看,基础类的子对象已封装到衍生类的对象里了。
基础类子对象要正确地初始化,就只能在构建器中执行初始化。在衍生类的构建器中,Java会自动插入对基础类构建器的调用。见下例:
//: Cartoon.java // Constructor calls during inheritance 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(); } } ///:~ output: Art constructor Drawing constructor Cartoon constructor
可以看到,构建是在基础类的“外部”进行的,所以基础类会在衍生类访问它之前得到正确的初始化。
初始化子类时会首先调用父类的构造器,初始化父类
如果父类没有默认的自变量,或是想调用某个有自变量的父类的构造器,则必须明确的写出对父类的调用代码。这时,需要用super关键字以及适当的自变量列表实现。见下例:
//: Chess.java // Inheritance, constructors and arguments 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(); } } ///:~
无论合成还是继承,都可以将子对象置于新类中。
如果想利用新类内部一个现有类的特性,而不想使用它的接口,通常应选择合成。也就是说,可以嵌入一个对象,能够使用它实现新类的特性。但新类的用户会看到已定义的接口,而不是嵌入对象的接口。考虑到这种效果,我们需在新类里嵌入现有类的private对象。
有些时候,我们想让类用户直接访问新类的合成。也就是说,需要将成员对象的属性变为public。成员对象会将自身隐藏起来,所以这是一种安全的做法。而且在用户知道我们准备合成一系列组件时,接口就更容易理解。
如下例:
//: Car.java // Composition with public objects class Engine { public void start() {} public void rev() {} public void stop() {} } class Wheel { public void inflate(int psi) {} } class Window { public void rollup() {} public void rolldown() {} } class Door { public Window window = new Window(); public void open() {} public void close() {} } public class Car { public Engine engine = new Engine(); public Wheel[] wheel = new Wheel[4]; public Door left = new Door(), right = new Door(); // 2-door Car() { for(int i = 0; i < 4; i++) wheel[i] = new Wheel(); } public static void main(String[] args) { Car car = new Car(); car.left.window.rollup(); car.wheel[0].inflate(72); } } ///:~
如果选择是引用继承,则是使用一个现成的类,制造出这个类的一个特殊版本。意思就是使用一个常规用途的类,并根据特定的需求对其进行定制。只要稍微思考下就知道自己不能用一个车辆对象来合成一辆汽车——汽车并不“包含”车辆;相反,它“属于”车辆的一种类别。“属于”(is a)关系是用继承来表达的,而“包含”(has a)关系是用合成来表达的。