一, 引用的複製和對象複製.
在編程中, 我們有時會用兩個引用指向同一個對象.
例如:
ArrayList a = new ArrayLIst();
ArrayList b = a;
看起來好像有a,b兩個容器, 實際上a,b是兩個引用, 它們都指向同1個Object的內存地址.
而對象複製是指:
在內存裏劃分一塊與指定對象相同內容的內存.
也就是說內存裏原理有1個Object的內存, 複製後就有兩個了...
二, 對象複製的簡便方法.
當然, 對象複製的實現十分簡單, 只需要實例化1個具有相同屬性的對象就ok了.
注意, 如果用 "==" 來講比較兩個引用是返回false, 因爲它們的地址不同.
舉個列子:
2.1 產品類Prod:
public class Prod {
private int prodID;
private String prodName;
public int getProdID() {
return prodID;
}
public void setProdID(int prodID) {
this.prodID = prodID;
}
public String getProdName() {
return prodName;
}
public void setProdName(String prodName) {
this.prodName = prodName;
}
@Override
public String toString(){
return "Prod: " + this.getProdID() + " , " + this.getProdName();
}
}
2.2 訂單類Order:
public class Order {
private int orderID;
private Prod prod;
private int amount;
public int getOrderID() {
return orderID;
}
public void setOrderID(int orderID) {
this.orderID = orderID;
}
public Prod getProd() {
return prod;
}
public void setProd(Prod prod) {
this.prod = prod;
}
public int getAmount() {
return amount;
}
public void setAmount(int amount) {
this.amount = amount;
}
public String toString(){
return "Order: " + this.getOrderID() + " , " + this.getAmount() + " " + this.getProd().toString();
}
}
2.3 客戶端代碼:
這裏我先實例化1個order類 od1, 然後在複製1個od1 的對象od2
Prod p1 = new Prod();
p1.setProdID(1);
p1.setProdName("Hammer");
Order od1 = new Order();
od1.setOrderID(1);
od1.setProd(p1);
od1.setAmount(20);
Prod p2 = new Prod();
p2.setProdID(1);
p2.setProdName("Hammer");
Order od2 = new Order();
od2.setOrderID(1);
od2.setProd(p1);
od2.setAmount(20);
System.out.println(od1);
System.out.println(od2);
輸出:
Order: 1 , 20 Prod: 1 , Hammer
Order: 1 , 20 Prod: 1 , Hammer
2.4 這種方法的缺點
首先這種寫法不夠優雅, 很多重複代碼.
其次, 這種複製方法調用了類的構造函數.
就如上面的例子, 即使Order類的構造函數是空的, 但是實際上構造函數的執行機制很複雜,.
構造函數最重要的動作就是給類裏的每個成員劃分內存空間. 畢竟java是類c語言, 相當於執行了很多次malloc()函數.
如果某些業務類的構造函數寫的很複雜, 就更耗費資源了.
而且
三, 利用Object.clone()方法來實現對象複製.
java裏所有對象都是直接或簡直繼承自基類Object.
而clone()是基類的Native 方法, 所謂Native方法可以認爲是java 內部特定方法, 它比非Native的執行效率要好.
也就是說, 利用Object.clone() 方法會比實例化1個相同內容對象效率要好.
3.1 Object.clone()方法和 Cloneable接口:
Object.clone() 方法有點特殊, 首先它是public方法, 也就是說它可以通過引用名和"."來調用.
但是它不能被子類繼承, 也就說, 出了Object類外所有java裏的class裏面都沒有clone()這個方法的.
如果要使用Object.clone()方法.
只能引用1個叫做 Cloneable裏的Interface, 然後重寫Cloneable接口裏的clone()方法, 在裏面調用object.clone().
這個接口就起了1個標記的作用, 幫組實現多態.
3.2 訂單類Order:
我們按在這個思路來修改上面的訂單類Order:public class Order implements Cloneable{
private int orderID;
private Prod prod;
private int amount;
public int getOrderID() {
return orderID;
}
public void setOrderID(int orderID) {
this.orderID = orderID;
}
public Prod getProd() {
return prod;
}
public void setProd(Prod prod) {
this.prod = prod;
}
public int getAmount() {
return amount;
}
public void setAmount(int amount) {
this.amount = amount;
}
public String toString(){
return "Order: " + this.getOrderID() + " , " + this.getAmount() + " " + this.getProd().toString();
}
@Override
public Object clone(){
Object o = null;
try{
o = super.clone();
}catch(Exception e){
e.printStackTrace();
}
return o;
}
}
值得注意的是, Object.clone() 回拋異常.
而, Prod類沒有任何修改.
3.3 客戶端代碼:
Prod p1 = new Prod();
p1.setProdID(2);
p1.setProdName("knife");
Order od1 = new Order();
od1.setOrderID(2);
od1.setAmount(30);
od1.setProd(p1);
Order od2 = (Order)od1.clone();
System.out.println(od1);
System.out.println(od2);
可見, 只需要執行一句
Order od2 = (Order)od1.clone();
就相當於 複製了1個對象.
3.4 UML圖
我們來看看這個例子的UML:
實際上這個就是原型模式(prototype)的UML圖了,
原來我們不知不覺得使用了原型模式.
四, 原型模式的定義
原型模式(protoType), 用原型實例制定創建對象的種類, 並且通過copy這些原型創建新的對象.
總覺得設計模式的定義都太過於簡單噁心了.
上面的
原型指的是 Cloneable這個接口.
原型實例指的是 Order這個類.
五, 淺複製和深複製
對象複製也分兩種.
兩個對象中的值類型成員屬性相等, 對象成員成員屬性實際上沒有複製,都是同一個對象.
深複製:
兩個對象中的值類型成員屬性相等, 對象成員屬性指向不同的 具有相同成員的 對象.
如上面的例子, 稍稍修改客戶端代碼:
Prod p1 = new Prod();
p1.setProdID(2);
p1.setProdName("knife");
Order od1 = new Order();
od1.setOrderID(2);
od1.setAmount(30);
od1.setProd(p1);
Order od2 = (Order)od1.clone();
System.out.println(od1);
System.out.println(od2);
od1.setOrderID(3);
od1.setAmount(40);
od1.getProd().setProdName("blade");
System.out.println(od1);
System.out.println(od2);
上面 構建了對象od1,
然後複製出了另1個對象od2,
然後修改od1的 值成員 和 對象成員
然後輸出od1, 和 od2 成員的值:
Order: 2 , 30 Prod: 2 , knife
Order: 2 , 30 Prod: 2 , knife
Order: 3 , 40 Prod: 2 , blade
Order: 2 , 30 Prod: 2 , blade
可見, 當od1 的id 和 amount的值被改成 3 和 40 後, od2 的id和 Amount的值並不受影響, 因爲它們的值類型成員屬性是相互獨立的.
但是當od1 的 prod屬性的內容被修改後, od2 的也被修改了(knife - > blade), 因爲 od1 和 od2 的 prod成員指向的都是同1個對象.
這就證明了:
Java裏的 Object.clone()方法實現的是淺複製.
六, 原型模式的深複製實現
接下來再次修改上面的例子, 令其實現成深複製
而且, 根據項目需要, 複製並不會複製Order的id, 也就是訂單號碼是唯一的, 只複製Prod和數量.
6.1 訂單類Order:
public class Order implements Cloneable{
private int orderID;
private Prod prod;
private int amount;
public int getOrderID() {
return orderID;
}
public void setOrderID(int orderID) {
this.orderID = orderID;
}
public Prod getProd() {
return prod;
}
public void setProd(Prod prod) {
this.prod = prod;
}
public int getAmount() {
return amount;
}
public void setAmount(int amount) {
this.amount = amount;
}
public String toString(){
return "Order: " + this.getOrderID() + " , " + this.getAmount() + " " + this.getProd().toString();
}
@Override
public Object clone(){
Prod p = new Prod();
p.setProdID(this.getProd().getProdID());
p.setProdName(this.getProd().getProdName());
Order o = new Order();
o.setOrderID(this.getOrderID() + 1);
o.setAmount(this.getAmount());
o.setProd(p);
return o;
}
}
客戶端輸出:
Order: 2 , 30 Prod: 2 , knife
Order: 3 , 30 Prod: 2 , knife
Order: 3 , 40 Prod: 2 , blade
Order: 3 , 30 Prod: 2 , knife
可以, 無論od1修改了什麼, od2 都不受影響
這種方法放棄了Object.clone(), Order類在重寫clone()方法裏還是使用了構造方法去實例化1個新的對象.
這種方法配置更加靈活(選擇性地複製成員)
當時放棄了Object.clone()的效能優點.
但是這仍然實現了原型模式(protoType)