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() {
prt("valve1 = " + valve1);
prt("valve2 = " + valve2);
prt("valve3 = " + valve3);
prt("valve4 = " + valve4);
prt("i = " + i);
prt("f = " + f);
prt("source = " + source);
}
public static void main(String[] args) {
SprinklerSystem x = new SprinklerSystem();
x.print();
}
static void prt(String s) {
System.out.println(s);
}
}
1.2、類合成的典型應用//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);
}
}由於汽車的裝配是故障分析時需要考慮的一項因素(並非只是基礎設計簡單的一部分),所以有助於客戶程序員理解如何使用類,而且類創建者的編程復雜程度也會大幅度降低。
如選擇繼承,就需要取得一個現成的類,並制作它的一個特殊版本。通常,這意味着我們准備使用一個常規用途的類,並根據特定的需求對其進行定制。只需稍加想象,就知道自己不能用一個車輛對象來合成一輛汽車——汽車並不“包含”車輛;相反,它“屬於”車輛的一種類別。“屬於”關系是用繼承來表達的,而“包含”關系是用合成來表達的。
2.1、類繼承的語法需要繼承的時候,我們會說:“這個新類和那個舊類差不多。”為了在代碼里表面這一觀念,需要給出類名。但在類主體的起始花括號之前,需要放置一個關鍵字extends,在后面跟隨“基礎類”的名字。若采取這種做法,就可自動獲得基礎類的所有數據成員以及方法。下面是一個例子:// Inheritance syntax & properties
class Cleanser {
private String s = new String("Cleaner");
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()");
}
public static void main(String[] args) {
Detergent x = new Detergent();
x.dilute();
x.apply();
x.scrub();
x.foam();
x.print();
System.out.println("Texting base class : ");
Cleanser.main(args);
}
}
無論Cleanser 還是Detergent 都包含了一個main()方法。我們可為自己的每個類都創建一個main()。通常建議大家象這樣進行編寫代碼,使自己的測試代碼能夠封裝到類內。即便在程序中含有數量眾多的類,但對於在命令行請求的public 類,只有main()才會得到調用。所以在這種情況下,當我們使用“java Detergent”的時候,調用的是Degergent.main()——即使Cleanser 並非一個public 類。采用這種將main()置入每個類的做法,可方便地為每個類都進行單元測試。而且在完成測試以后,毋需將main()刪
去;可把它保留下來,用於以后的測試。
在這里,大家可看到Deteregent.main()對Cleanser.main()的調用是明確進行的。注意Cleanser 在它的接口中含有一系列方法:append(),dilute(),apply(),scrub()以及print()。由於Detergent 是從Cleanser 衍生出來的(通過extends 關鍵字),所以它會自動獲得接口內的所有這些方法——即使我們在Detergent 里並未看到對它們的明確定義。這樣一來,就可將繼承想象成“對接口的重復利用”或者“接口的再生”(以后的實施細節可以自由設置,但那並非我們強調的重點)。
正如在scrub()里看到的那樣,可以獲得在基礎類里定義的一個方法,並對其進行修改。在這種情況下,我們通常想在新版本里調用來自基礎類的方法。但在scrub()里,不可只是簡單地發出對scrub()的調用。那樣便造成了遞歸調用,我們不願看到這一情況。為解決這個問題,Java 提供了一個super 關鍵字,它引用當前類已從中繼承的一個“超類”(Superclass)。所以表達式super.scrub()調用的是方法scrub()的基礎類版本。進行繼承時,我們並不限於只能使用基礎類的方法。亦可在衍生出來的類里加入自己的新方法。這時采取的做法與在普通類里添加其他任何方法是完全一樣的:只需簡單地定義它即可。extends 關鍵字提醒我們准備將新方法加入基礎類的接口里,對其進行“擴展”。foam()便是這種做法的一個產物。
2.2、上塑造型繼承是對新類和基礎類之間的關系的一種表達。可這樣總結該關系:“新類屬於現有類的一種類型”。這種表達並不僅僅是對繼承的一種形象化解釋,繼承是直接由語言提供支持的。作為一個例子,大家可考慮一個名為Instrument 的基礎類,它用於表示樂器;另一個衍生類叫作Wind。由於繼承意味着基礎類的所有方法亦可在衍生出來的類中使用,所以我們發給基礎類的任何消息亦可發給衍生類。若Instrument 類有一個play()方法,則Wind 設備也會有這個方法。這意味着我們能肯定地認為一個Wind 對象也是Instrument 的一種類型。下面這個例子揭示出編譯器如何提供對這一概念的支持:class Instrument {
public void play() {
}
static void tune(Instrument i) {
// ...
i.play();
}
}
// Wind objects are instruments
// because they have the same interface:
class Wind extends Instrument {
public static void main(String[] args) {
Wind flute = new Wind();
Instrument.tune(flute); // Upcasting
}
}
程序正常運行,沒有報錯。這個例子中最有趣的無疑是tune()方法,它能接受一個Instrument 句柄。但在Wind.main()中,tune()方法是通過為其賦予一個Wind 句柄來調用的。由於Java 對類型檢查特別嚴格,所以大家可能會感到很奇怪,為什么接收一種類型的方法也能接收另一種類型呢?但是,我們一定要認識到一個Wind 對象也是一個Instrument 對象。而且對於不在Wind 中的一個Instrument(樂器),沒有方法可以由tune()調用。在tune()中,代碼適用於Instrument 以及從Instrument 衍生出來的任何東西。在這里,我們將從一個Wind 句柄轉換成一個Instrument 句柄的行為叫作“上溯造型”。
2.3、何謂“上溯造型”由於造型的方向是從衍生類到基礎類,箭頭朝上,所以通常把它叫作“上溯造型”,即Upcasting。上溯造型肯定是安全的,因為我們是從一個更特殊的類型到一個更常規的類型。換言之,衍生類是基礎類的一個超集。它可以包含比基礎類更多的方法,但它至少包含了基礎類的方法。進行上溯造型的時候,類接口可能出現的唯一一個問題是它可能丟失方法,而不是贏得這些方法。這便是在沒有任何明確的造型或者其他特殊標注的情況下,編譯器為什么允許上溯造型的原因所在。