深入理解Java中的克隆
前言
Java克隆(Clone)是Java語(yǔ)言的特性之一,但在實(shí)際中應(yīng)用比較少見。但有時(shí)候用克隆會(huì)更方便更有效率。
對(duì)于克隆(Clone),Java有一些限制:
1、被克隆的類必須自己實(shí)現(xiàn)Cloneable 接口,以指示 Object.clone()
方法可以合法地對(duì)該類實(shí)例進(jìn)行按字段復(fù)制。Cloneable 接口實(shí)際上是個(gè)標(biāo)識(shí)接口,沒有任何接口方法。
2、實(shí)現(xiàn)Cloneable接口的類應(yīng)該使用公共方法重寫 Object.clone
(它是受保護(hù)的)。某個(gè)對(duì)象實(shí)現(xiàn)了此接口就克隆它是不可能的。即使 clone 方法是反射性調(diào)用的,也無法保證它將獲得成功。
3、在Java.lang.Object
類中克隆方法是這么定義的:
protected Object clone() throws CloneNotSupportedException
創(chuàng)建并返回此對(duì)象的一個(gè)副本。表明是一個(gè)受保護(hù)的方法,同一個(gè)包中可見。
按照慣例,返回的對(duì)象應(yīng)該通過調(diào)用 super.clone
獲得。
Java中的賦值
在Java中,賦值是很常用的,一個(gè)簡(jiǎn)單的賦值如下
//原始類型 int a = 1; int b = a; //引用類型 String[] weekdays = new String[5]; String[] gongzuori = weekdays;//僅拷貝引用
在上述代碼中。
1、如果是原始數(shù)據(jù)類型,賦值傳遞的為真實(shí)的值
2、如果是引用數(shù)據(jù)類型,賦值傳遞的為對(duì)象的引用,而不是對(duì)象。
了解了數(shù)據(jù)類型和引用類型的這個(gè)區(qū)別,便于我們了解clone。
Clone
在Java中,clone是將已有對(duì)象在內(nèi)存中復(fù)制出另一個(gè)與之相同的對(duì)象的過程。java中的克隆為逐域復(fù)制。
在Java中想要支持clone方法,需要首先實(shí)現(xiàn)Cloneable接口
Cloneable其實(shí)是有點(diǎn)奇怪的,它不同與我們常用到的接口,它內(nèi)部不包含任何方法,它僅僅是一個(gè)標(biāo)記接口。
其源碼如下
public interface Cloneable { }
關(guān)于cloneable,需要注意的
1、如果想要支持clone,就需要實(shí)現(xiàn)Cloneable 接口
2、如果沒有實(shí)現(xiàn)Cloneable接口的調(diào)用clone方法,會(huì)拋出CloneNotSupportedException異常。
然后是重寫clone方法,并修改成public訪問級(jí)別
static class CloneableImp implements Cloneable { public int count; public Child child; @Override public Object clone() throws CloneNotSupportedException { return super.clone(); } }
調(diào)用clone方法復(fù)制對(duì)象
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(); }
淺拷貝
上面的代碼實(shí)現(xiàn)的clone實(shí)際上是屬于淺拷貝(Shallow Copy)。
關(guān)于淺拷貝,你該了解的
1、使用默認(rèn)的clone方法
2、對(duì)于原始數(shù)據(jù)域進(jìn)行值拷貝
3、對(duì)于引用類型僅拷貝引用
4、執(zhí)行快,效率高
5、不能做到數(shù)據(jù)的100%分離。
6、如果一個(gè)對(duì)象只包含原始數(shù)據(jù)域或者不可變對(duì)象域,推薦使用淺拷貝。
關(guān)于無法做到數(shù)據(jù)分離,我們可以使用這段代碼驗(yàn)證
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
得到的結(jié)果是
main imp1.child.name=Bob
原因是淺拷貝并沒有做到數(shù)據(jù)的100%分離,imp1和imp2共享同一個(gè)Child對(duì)象,所以一個(gè)修改會(huì)影響到另一個(gè)。
深拷貝
深拷貝可以解決數(shù)據(jù)100%分離的問題。只需要對(duì)上面代碼進(jìn)行一些修改即可。
1、Child實(shí)現(xiàn)Cloneable接口。
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方法,調(diào)用數(shù)據(jù)域的clone方法。
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; } }
當(dāng)我們?cè)俅涡薷?code>imp2.child.name就不會(huì)影響到imp1.child.name
的值了,因?yàn)閕mp1和imp2各自擁有自己的child對(duì)象,因?yàn)樽龅搅藬?shù)據(jù)的100%隔離。
關(guān)于深拷貝的一些特點(diǎn)
1、需要重寫clone方法,不僅僅只調(diào)用父類的方法,還需調(diào)用屬性的clone方法
2、做到了原對(duì)象與克隆對(duì)象之間100%數(shù)據(jù)分離
3、如果是對(duì)象存在引用類型的屬性,建議使用深拷貝
4、深拷貝比淺拷貝要更加耗時(shí),效率更低
為什么使用克隆
很重要并且常見的常見就是:某個(gè)API需要提供一個(gè)List集合,但是又不希望調(diào)用者的修改影響到自身的變化,因此需要克隆一份對(duì)象,以此達(dá)到數(shù)據(jù)隔離的目的。
應(yīng)盡量避免clone
1.通常情況下,實(shí)現(xiàn)接口是為了表明類可以為它的客戶做些什么,而Cloneable僅僅是一個(gè)標(biāo)記接口,而且還改變了超類中的手保護(hù)的方法的行為,是接口的一種極端非典型的用法,不值得效仿。
2.Clone方法約定及其脆弱 clone方法的Javadoc描述有點(diǎn)曖昧模糊,如下為 Java SE8的約定
clone方法創(chuàng)建并返回該對(duì)象的一個(gè)拷貝。而拷貝的精確含義取決于該對(duì)象的類。一般的含義是,對(duì)于任何對(duì)象x,表達(dá)式
x.clone() != x 為 true x.clone().getClass() == x.getClass()
也返回true,但非必須 x.clone().equals(x)
也返回true,但也不是必須的
上面的第二個(gè)和第三個(gè)表達(dá)式很容易就返回false。因而唯一能保證永久為true的就是表達(dá)式一,即兩個(gè)對(duì)象為獨(dú)立的對(duì)象。
3.可變對(duì)象final域 在克隆方法中,如果我們需要對(duì)可變對(duì)象的final域也進(jìn)行拷貝,由于final的限制,所以實(shí)際上是無法編譯通過的。因此為了實(shí)現(xiàn)克隆,我們需要考慮舍去該可變對(duì)象域的final關(guān)鍵字。
4.線程安全 如果你決定用線程安全的類實(shí)現(xiàn)Cloneable接口,需要保證它的clone方法做好同步工作。默認(rèn)的Object.clone
方法是沒有做同步的。
總的來說,java中的clone方法實(shí)際上并不是完善的,建議盡量避免使用。如下是一些替代方案。
Copy constructors
使用復(fù)制構(gòu)造器也可以實(shí)現(xiàn)對(duì)象的拷貝。
1、復(fù)制構(gòu)造器也是構(gòu)造器的一種
2、只接受一個(gè)參數(shù),參數(shù)類型為當(dāng)前的類
3、目的是生成一個(gè)與參數(shù)相同的新對(duì)象
4、復(fù)制構(gòu)造器相比clone方法的優(yōu)勢(shì)是簡(jiǎn)單,易于實(shí)現(xiàn)。
一段使用了復(fù)制構(gòu)造器的代碼示例
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; } }
注意,上面的代碼實(shí)現(xiàn)為淺拷貝,如果想要實(shí)現(xiàn)深拷貝,參考如下代碼
//copy constructor public Car(Car car) { Wheel wheel = new Wheel(); wheel.brand = car.wheel.brand; this.wheel = wheel; this.manufacturer = car.manufacturer; }
為了更加便捷,我們還可以為上述類增加一個(gè)靜態(tài)的方法
public static Car newInstance(Car car) { return new Car(car); }
使用Serializable實(shí)現(xiàn)深拷貝
其實(shí),使用序列化也可以實(shí)現(xiàn)對(duì)象的深拷貝。簡(jiǎn)略代碼如下
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必須實(shí)現(xiàn)Serializable接口
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 + "]"; } }
使用示例兼測(cè)試代碼
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); } //輸出結(jié)果:example.child=Child [name=Example];copy.child=Child [name=Copied]
由輸出結(jié)果來看,copy對(duì)象的child值修改不影響example對(duì)象的child值,即使用序列化可以實(shí)現(xiàn)對(duì)象的深拷貝。
總結(jié)
以上就是Java中克隆的全部?jī)?nèi)容,希望本文對(duì)大家學(xué)習(xí)Java能有所幫助。
- Java中對(duì)象的深復(fù)制(深克?。┖蜏\復(fù)制(淺克?。┙榻B
- 基于序列化存取實(shí)現(xiàn)java對(duì)象深度克隆的方法詳解
- 深入JAVA對(duì)象深度克隆的詳解
- 解析JAVA深度克隆與淺度克隆的區(qū)別詳解
- Java編程實(shí)現(xiàn)對(duì)象克?。◤?fù)制)代碼詳解
- Java中對(duì)象的序列化方式克隆詳解
- 淺談Java中的克隆close()和賦值引用的區(qū)別
- java 對(duì)象的克隆(淺克隆和深克?。?/a>
- 淺析Java中clone()方法淺克隆與深度克隆
- Java實(shí)現(xiàn)克隆的三種方式實(shí)例總結(jié)
相關(guān)文章
SpringCloud之Zuul網(wǎng)關(guān)原理及其配置講解
這篇文章主要介紹了SpringCloud之Zuul網(wǎng)關(guān)原理及其配置講解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03Spring Boot FeignClient 如何捕獲業(yè)務(wù)異常信息
這篇文章主要介紹了Spring Boot FeignClient 如何捕獲業(yè)務(wù)異常信息的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06Spring cloud Eureka注冊(cè)中心搭建的方法
這篇文章主要介紹了Spring cloud Eureka注冊(cè)中心搭建的方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-04-04Java 8 Function函數(shù)式接口及函數(shù)式接口實(shí)例
函數(shù)式接口(Functional Interface)就是一個(gè)有且僅有一個(gè)抽象方法,但是可以有多個(gè)非抽象方法的接口。接下來通過本文給大家介紹Java 8 Function函數(shù)式接口及函數(shù)式接口實(shí)例代碼,需要的朋友可以參考下2018-05-05Springboot項(xiàng)目升級(jí)2.2.x升至2.7.x的示例代碼
本文主要介紹了Springboot項(xiàng)目升級(jí)2.2.x升至2.7.x的示例代碼,會(huì)有很多的坑,具有一定的參考價(jià)值,感興趣的可以了解一下2023-09-09