克隆看起來要求進行非常復雜的設置,似乎還該有另一種替代方案。一個辦法是制作特殊的構建器,令其負責復制一個對象。在C++中,這叫作“副本構建器”。剛開始的時候,這好象是一種非常顯然的解決方案(如果你是C++程序員,這個方法就更顯親切)。下面是一個實際的例子:
//: CopyConstructor.java // A constructor for copying an object // of the same type, as an attempt to create // a local copy. class FruitQualities { private int weight; private int color; private int firmness; private int ripeness; private int smell; // etc. FruitQualities() { // Default constructor // do something meaningful... } // Other constructors: // ... // Copy constructor: FruitQualities(FruitQualities f) { weight = f.weight; color = f.color; firmness = f.firmness; ripeness = f.ripeness; smell = f.smell; // etc. } } class Seed { // Members... Seed() { /* Default constructor */ } Seed(Seed s) { /* Copy constructor */ } } class Fruit { private FruitQualities fq; private int seeds; private Seed[] s; Fruit(FruitQualities q, int seedCount) { fq = q; seeds = seedCount; s = new Seed[seeds]; for(int i = 0; i < seeds; i++) s[i] = new Seed(); } // Other constructors: // ... // Copy constructor: Fruit(Fruit f) { fq = new FruitQualities(f.fq); seeds = f.seeds; // Call all Seed copy-constructors: for(int i = 0; i < seeds; i++) s[i] = new Seed(f.s[i]); // Other copy-construction activities... } // To allow derived constructors (or other // methods) to put in different qualities: protected void addQualities(FruitQualities q) { fq = q; } protected FruitQualities getQualities() { return fq; } } class Tomato extends Fruit { Tomato() { super(new FruitQualities(), 100); } Tomato(Tomato t) { // Copy-constructor super(t); // Upcast for base copy-constructor // Other copy-construction activities... } } class ZebraQualities extends FruitQualities { private int stripedness; ZebraQualities() { // Default constructor // do something meaningful... } ZebraQualities(ZebraQualities z) { super(z); stripedness = z.stripedness; } } class GreenZebra extends Tomato { GreenZebra() { addQualities(new ZebraQualities()); } GreenZebra(GreenZebra g) { super(g); // Calls Tomato(Tomato) // Restore the right qualities: addQualities(new ZebraQualities()); } void evaluate() { ZebraQualities zq = (ZebraQualities)getQualities(); // Do something with the qualities // ... } } public class CopyConstructor { public static void ripen(Tomato t) { // Use the "copy constructor": t = new Tomato(t); System.out.println("In ripen, t is a " + t.getClass().getName()); } public static void slice(Fruit f) { f = new Fruit(f); // Hmmm... will this work? System.out.println("In slice, f is a " + f.getClass().getName()); } public static void main(String[] args) { Tomato tomato = new Tomato(); ripen(tomato); // OK slice(tomato); // OOPS! GreenZebra g = new GreenZebra(); ripen(g); // OOPS! slice(g); // OOPS! g.evaluate(); } } ///:~
這個例子第一眼看上去顯得有點奇怪。不同水果的質量肯定有所區別,但為什麼只是把代表那些質量的數據成員直接置入Fruit(水果)類?有兩方面可能的原因。第一個是我們可能想簡便地插入或修改質量。注意Fruit有一個protected(受到保護的)addQualities()方法,它允許衍生類來進行這些插入或修改操作(大家或許會認為最合乎邏輯的做法是在Fruit中使用一個protected構建器,用它獲取FruitQualities參數,但構建器不能繼承,所以不可在第二級或級數更深的類中使用它)。通過將水果的質量置入一個獨立的類,可以得到更大的靈活性,其中包括可以在特定Fruit對象的存在期間中途更改質量。
之所以將FruitQualities設為一個獨立的對象,另一個原因是考慮到我們有時希望添加新的質量,或者通過繼承與多形性改變行為。注意對GreenZebra來說(這實際是西紅柿的一類——我已栽種成功,它們簡直令人難以置信),構建器會調用addQualities(),並為其傳遞一個ZebraQualities對象。該對象是從FruitQualities衍生出來的,所以能與基礎類中的FruitQualities句柄聯系在一起。當然,一旦GreenZebra使用FruitQualities,就必須將其下溯造型成為正確的類型(就象evaluate()中展示的那樣),但它肯定知道類型是ZebraQualities。
大家也看到有一個Seed(種子)類,Fruit(大家都知道,水果含有自己的種子)包含了一個Seed數組。
最後,注意每個類都有一個副本構建器,而且每個副本構建器都必須關心為基礎類和成員對象調用副本構建器的問題,從而獲得“深層復制”的效果。對副本構建器的測試是在CopyConstructor類內進行的。方法ripen()需要獲取一個Tomato參數,並對其執行副本構建工作,以便復制對象:
t = new Tomato(t);
而slice()需要獲取一個更常規的Fruit對象,而且對它進行復制:
f = new Fruit(f);
它們都在main()中伴隨不同種類的Fruit進行測試。下面是輸出結果:
In ripen, t is a Tomato In slice, f is a Fruit In ripen, t is a Tomato In slice, f is a Fruit
從中可以看出一個問題。在slice()內部對Tomato進行了副本構建工作以後,結果便不再是一個Tomato對象,而只是一個Fruit。它已丟失了作為一個Tomato(西紅柿)的所有特征。此外,如果采用一個GreenZebra,ripen()和slice()會把它分別轉換成一個Tomato和一個Fruit。所以非常不幸,假如想制作對象的一個本地副本,Java中的副本構建器便不是特別適合我們。
1. 為什麼在C++的作用比在Java中大?
副本構建器是C++的一個基本構成部分,因為它能自動產生對象的一個本地副本。但前面的例子確實證明了它不適合在Java中使用,為什麼呢?在Java中,我們操控的一切東西都是句柄,而在C++中,卻可以使用類似於句柄的東西,也能直接傳遞對象。這時便要用到C++的副本構建器:只要想獲得一個對象,並按值傳遞它,就可以復制對象。所以它在C++裡能很好地工作,但應注意這套機制在Java裡是很不通的,所以不要用它。