複製建構子是 C++ 基礎機制,可自動產生本地副本。但在 Java 並不適用。以下範例展示完整複製建構子實作與其失效場景:
//: CopyConstructor.java
class FruitQualities { /* ... 複製建構子複製所有欄位 ... */ }
class Seed { /* ... 複製建構子 ... */ }
class Fruit {
private FruitQualities fq;
private int seeds;
private Seed[] s;
Fruit(FruitQualities q, int seedCount) { /* ... */ }
// 複製建構子:深層複製 fq 與每個 Seed
Fruit(Fruit f) {
fq = new FruitQualities(f.fq);
seeds = f.seeds;
for(int i = 0; i < seeds; i++)
s[i] = new Seed(f.s[i]);
}
protected void addQualities(FruitQualities q) { fq = q; }
protected FruitQualities getQualities() { return fq; }
}
class Tomato extends Fruit {
Tomato() { super(new FruitQualities(), 100); }
Tomato(Tomato t) { super(t); /* ... */ }
}
class ZebraQualities extends FruitQualities { /* ... */ }
class GreenZebra extends Tomato {
GreenZebra() { addQualities(new ZebraQualities()); }
GreenZebra(GreenZebra g) {
super(g); // 呼叫 Tomato(Tomato)
addQualities(new ZebraQualities()); // 恢復正確品質
}
void evaluate() {
ZebraQualities zq = (ZebraQualities)getQualities();
// ...
}
}
public class CopyConstructor {
static void ripen(Tomato t) {
t = new Tomato(t); // 使用複製建構子
System.out.println("In ripen, t is a " + t.getClass().getName());
}
static void slice(Fruit f) {
f = new Fruit(f); // 嘗試複製
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();
}
} ///:~
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() 接收 Fruit 參數並用 Fruit 複製建構子複製,導致 Tomato 或 GreenZebra 特有特徵遺失——物件被「切割」為基類型。ripen() 雖能正確複製 Tomato,但面對 GreenZebra 仍退化為 Tomato。
C++ 可直接傳遞物件(by value),複製建構子自動觸發。Java 一切皆參考(handle),無 by value 機制。C++ 複製建構子配合 by value 參數傳遞完美運作;Java 無此語意基礎,強行模仿反而破壞多型。
結論:Java 中不要使用複製建構子製作本地副本。