前言
Java克隆(Clone)是Java語言的特性之一,但在實際中應用比較少見。但有時候用克隆會更方便更有效率。
對于克隆(Clone),Java有一些限制:
1、被克隆的類必須自己實現Cloneable 接口,以指示 Object.clone()
方法可以合法地對該類實例進行按字段復制。Cloneable 接口實際上是個標識接口,沒有任何接口方法。
2、實現Cloneable接口的類應該使用公共方法重寫 Object.clone
(它是受保護的)。某個對象實現了此接口就克隆它是不可能的。即使 clone 方法是反射性調用的,也無法保證它將獲得成功。
3、在Java.lang.Object
類中克隆方法是這么定義的:
1
2
|
protected Object clone() throws CloneNotSupportedException |
創建并返回此對象的一個副本。表明是一個受保護的方法,同一個包中可見。
按照慣例,返回的對象應該通過調用 super.clone
獲得。
Java中的賦值
在Java中,賦值是很常用的,一個簡單的賦值如下
1
2
3
4
5
6
7
|
//原始類型 int a = 1 ; int b = a; //引用類型 String[] weekdays = new String[ 5 ]; String[] gongzuori = weekdays; //僅拷貝引用 |
在上述代碼中。
1、如果是原始數據類型,賦值傳遞的為真實的值
2、如果是引用數據類型,賦值傳遞的為對象的引用,而不是對象。
了解了數據類型和引用類型的這個區別,便于我們了解clone。
Clone
在Java中,clone是將已有對象在內存中復制出另一個與之相同的對象的過程。java中的克隆為逐域復制。
在Java中想要支持clone方法,需要首先實現Cloneable接口
Cloneable其實是有點奇怪的,它不同與我們常用到的接口,它內部不包含任何方法,它僅僅是一個標記接口。
其源碼如下
1
2
|
public interface Cloneable { } |
關于cloneable,需要注意的
1、如果想要支持clone,就需要實現Cloneable 接口
2、如果沒有實現Cloneable接口的調用clone方法,會拋出CloneNotSupportedException異常。
然后是重寫clone方法,并修改成public訪問級別
1
2
3
4
5
6
7
8
9
10
|
static class CloneableImp implements Cloneable { public int count; public Child child; @Override public Object clone() throws CloneNotSupportedException { return super .clone(); } } |
調用clone方法復制對象
1
2
3
4
5
6
7
8
9
|
CloneableImp imp1 = new CloneableImp(); imp1.child = new Child( "Andy" ); try { Object obj = imp1.clone(); CloneableImp imp2 = (CloneableImp)obj; System.out.println( "main imp2.child.name=" + imp2.child.name); } catch (CloneNotSupportedException e) { e.printStackTrace(); } |
淺拷貝
上面的代碼實現的clone實際上是屬于淺拷貝(Shallow Copy)。
關于淺拷貝,你該了解的
1、使用默認的clone方法
2、對于原始數據域進行值拷貝
3、對于引用類型僅拷貝引用
4、執行快,效率高
5、不能做到數據的100%分離。
6、如果一個對象只包含原始數據域或者不可變對象域,推薦使用淺拷貝。
關于無法做到數據分離,我們可以使用這段代碼驗證
1
2
3
4
5
6
7
8
9
10
11
|
CloneableImp imp1 = new CloneableImp(); imp1.child = new Child( "Andy" ); try { Object obj = imp1.clone(); CloneableImp imp2 = (CloneableImp)obj; imp2.child.name = "Bob" ; System.out.println( "main imp1.child.name=" + imp1.child.name); } catch (CloneNotSupportedException e) { e.printStackTrace(); } |
上述代碼我們使用了imp1的clone方法克隆出imp2,然后修改 imp2.child.name
為 Bob,然后打印imp1.child.name
得到的結果是
1
|
main imp1.child.name=Bob |
原因是淺拷貝并沒有做到數據的100%分離,imp1和imp2共享同一個Child對象,所以一個修改會影響到另一個。
深拷貝
深拷貝可以解決數據100%分離的問題。只需要對上面代碼進行一些修改即可。
1、Child實現Cloneable接口。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
public class Child implements Cloneable{ public String name; public Child(String name) { this .name = name; } @Override public String toString() { return "Child [name=" + name + "]" ; } @Override protected Object clone() throws CloneNotSupportedException { return super .clone(); } } |
2.重寫clone方法,調用數據域的clone方法。
1
2
3
4
5
6
7
8
9
10
11
12
|
static class CloneableImp implements Cloneable { public int count; public Child child; @Override public Object clone() throws CloneNotSupportedException { CloneableImp obj = (CloneableImp) super .clone(); obj.child = (Child) child.clone(); return obj; } } |
當我們再次修改imp2.child.name
就不會影響到imp1.child.name
的值了,因為imp1和imp2各自擁有自己的child對象,因為做到了數據的100%隔離。
關于深拷貝的一些特點
1、需要重寫clone方法,不僅僅只調用父類的方法,還需調用屬性的clone方法
2、做到了原對象與克隆對象之間100%數據分離
3、如果是對象存在引用類型的屬性,建議使用深拷貝
4、深拷貝比淺拷貝要更加耗時,效率更低
為什么使用克隆
很重要并且常見的常見就是:某個API需要提供一個List集合,但是又不希望調用者的修改影響到自身的變化,因此需要克隆一份對象,以此達到數據隔離的目的。
應盡量避免clone
1.通常情況下,實現接口是為了表明類可以為它的客戶做些什么,而Cloneable僅僅是一個標記接口,而且還改變了超類中的手保護的方法的行為,是接口的一種極端非典型的用法,不值得效仿。
2.Clone方法約定及其脆弱 clone方法的Javadoc描述有點曖昧模糊,如下為 Java SE8的約定
clone方法創建并返回該對象的一個拷貝。而拷貝的精確含義取決于該對象的類。一般的含義是,對于任何對象x,表達式
x.clone() != x 為 true x.clone().getClass() == x.getClass()
也返回true,但非必須 x.clone().equals(x)
也返回true,但也不是必須的
上面的第二個和第三個表達式很容易就返回false。因而唯一能保證永久為true的就是表達式一,即兩個對象為獨立的對象。
3.可變對象final域 在克隆方法中,如果我們需要對可變對象的final域也進行拷貝,由于final的限制,所以實際上是無法編譯通過的。因此為了實現克隆,我們需要考慮舍去該可變對象域的final關鍵字。
4.線程安全 如果你決定用線程安全的類實現Cloneable接口,需要保證它的clone方法做好同步工作。默認的Object.clone
方法是沒有做同步的。
總的來說,java中的clone方法實際上并不是完善的,建議盡量避免使用。如下是一些替代方案。
Copy constructors
使用復制構造器也可以實現對象的拷貝。
1、復制構造器也是構造器的一種
2、只接受一個參數,參數類型為當前的類
3、目的是生成一個與參數相同的新對象
4、復制構造器相比clone方法的優勢是簡單,易于實現。
一段使用了復制構造器的代碼示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
public class Car { Wheel wheel; String manufacturer; public Car(Wheel wheel, String manufacturer) { this .wheel = wheel; this .manufacturer = manufacturer; } //copy constructor public Car(Car car) { this (car.wheel, car.manufacturer); } public static class Wheel { String brand; } } |
注意,上面的代碼實現為淺拷貝,如果想要實現深拷貝,參考如下代碼
1
2
3
4
5
6
7
8
|
//copy constructor public Car(Car car) { Wheel wheel = new Wheel(); wheel.brand = car.wheel.brand; this .wheel = wheel; this .manufacturer = car.manufacturer; } |
為了更加便捷,我們還可以為上述類增加一個靜態的方法
1
2
3
|
public static Car newInstance(Car car) { return new Car(car); } |
使用Serializable實現深拷貝
其實,使用序列化也可以實現對象的深拷貝。簡略代碼如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
public class DeepCopyExample implements Serializable{ private static final long serialVersionUID = 6098694917984051357L; public Child child; public DeepCopyExample copy() { DeepCopyExample copy = null ; try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject( this ); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bais); copy = (DeepCopyExample) ois.readObject(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } return copy; } } |
其中,Child必須實現Serializable接口
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public class Child implements Serializable{ private static final long serialVersionUID = 6832122780722711261L; public String name = "" ; public Child(String name) { this .name = name; } @Override public String toString() { return "Child [name=" + name + "]" ; } } |
使用示例兼測試代碼
1
2
3
4
5
6
7
8
9
|
DeepCopyExample example = new DeepCopyExample(); example.child = new Child( "Example" ); DeepCopyExample copy = example.copy(); if (copy != null ) { copy.child.name = "Copied" ; System.out.println( "example.child=" + example.child + ";copy.child=" + copy.child); } //輸出結果:example.child=Child [name=Example];copy.child=Child [name=Copied] |
由輸出結果來看,copy對象的child值修改不影響example對象的child值,即使用序列化可以實現對象的深拷貝。
總結
以上就是Java中克隆的全部內容,希望本文對大家學習Java能有所幫助。