国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > Java > 正文

深入理解Java中的克隆

2019-11-26 13:58:40
字體:
供稿:網(wǎng)友

前言

Java克隆(Clone)是Java語言的特性之一,但在實(shí)際中應(yīng)用比較少見。但有時(shí)候用克隆會更方便更有效率。

對于克隆(Clone),Java有一些限制:

      1、被克隆的類必須自己實(shí)現(xiàn)Cloneable 接口,以指示 Object.clone() 方法可以合法地對該類實(shí)例進(jìn)行按字段復(fù)制。Cloneable 接口實(shí)際上是個(gè)標(biāo)識接口,沒有任何接口方法。

      2、實(shí)現(xiàn)Cloneable接口的類應(yīng)該使用公共方法重寫 Object.clone(它是受保護(hù)的)。某個(gè)對象實(shí)現(xiàn)了此接口就克隆它是不可能的。即使 clone 方法是反射性調(diào)用的,也無法保證它將獲得成功。

      3、在Java.lang.Object類中克隆方法是這么定義的:

        protected Object clone()        throws CloneNotSupportedException

創(chuàng)建并返回此對象的一個(gè)副本。表明是一個(gè)受保護(hù)的方法,同一個(gè)包中可見。

按照慣例,返回的對象應(yīng)該通過調(diào)用 super.clone 獲得。

Java中的賦值

在Java中,賦值是很常用的,一個(gè)簡單的賦值如下

//原始類型int a = 1;int b = a;//引用類型String[] weekdays = new String[5];String[] gongzuori = weekdays;//僅拷貝引用

在上述代碼中。

      1、如果是原始數(shù)據(jù)類型,賦值傳遞的為真實(shí)的值

      2、如果是引用數(shù)據(jù)類型,賦值傳遞的為對象的引用,而不是對象。

了解了數(shù)據(jù)類型和引用類型的這個(gè)區(qū)別,便于我們了解clone。

Clone

在Java中,clone是將已有對象在內(nèi)存中復(fù)制出另一個(gè)與之相同的對象的過程。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方法,會拋出CloneNotSupportedException異常。

然后是重寫clone方法,并修改成public訪問級別

static class CloneableImp implements Cloneable { public int count; public Child child;       @Override public Object clone() throws CloneNotSupportedException {   return super.clone(); }}

調(diào)用clone方法復(fù)制對象

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、對于原始數(shù)據(jù)域進(jìn)行值拷貝

     3、對于引用類型僅拷貝引用

     4、執(zhí)行快,效率高

     5、不能做到數(shù)據(jù)的100%分離。

     6、如果一個(gè)對象只包含原始數(shù)據(jù)域或者不可變對象域,推薦使用淺拷貝。

關(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對象,所以一個(gè)修改會影響到另一個(gè)。

深拷貝

深拷貝可以解決數(shù)據(jù)100%分離的問題。只需要對上面代碼進(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)我們再次修改imp2.child.name就不會影響到imp1.child.name的值了,因?yàn)閕mp1和imp2各自擁有自己的child對象,因?yàn)樽龅搅藬?shù)據(jù)的100%隔離。

關(guān)于深拷貝的一些特點(diǎn)

      1、需要重寫clone方法,不僅僅只調(diào)用父類的方法,還需調(diào)用屬性的clone方法

      2、做到了原對象與克隆對象之間100%數(shù)據(jù)分離

      3、如果是對象存在引用類型的屬性,建議使用深拷貝

      4、深拷貝比淺拷貝要更加耗時(shí),效率更低

為什么使用克隆

很重要并且常見的常見就是:某個(gè)API需要提供一個(gè)List集合,但是又不希望調(diào)用者的修改影響到自身的變化,因此需要克隆一份對象,以此達(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)建并返回該對象的一個(gè)拷貝。而拷貝的精確含義取決于該對象的類。一般的含義是,對于任何對象x,表達(dá)式

          x.clone() != x 為 true x.clone().getClass() == x.getClass() 也返回true,但非必須 x.clone().equals(x) 也返回true,但也不是必須的

上面的第二個(gè)和第三個(gè)表達(dá)式很容易就返回false。因而唯一能保證永久為true的就是表達(dá)式一,即兩個(gè)對象為獨(dú)立的對象。

       3.可變對象final域 在克隆方法中,如果我們需要對可變對象的final域也進(jìn)行拷貝,由于final的限制,所以實(shí)際上是無法編譯通過的。因此為了實(shí)現(xiàn)克隆,我們需要考慮舍去該可變對象域的final關(guān)鍵字。

       4.線程安全 如果你決定用線程安全的類實(shí)現(xiàn)Cloneable接口,需要保證它的clone方法做好同步工作。默認(rèn)的Object.clone方法是沒有做同步的。

總的來說,java中的clone方法實(shí)際上并不是完善的,建議盡量避免使用。如下是一些替代方案。

Copy constructors

使用復(fù)制構(gòu)造器也可以實(shí)現(xiàn)對象的拷貝。

      1、復(fù)制構(gòu)造器也是構(gòu)造器的一種

      2、只接受一個(gè)參數(shù),參數(shù)類型為當(dāng)前的類

      3、目的是生成一個(gè)與參數(shù)相同的新對象

      4、復(fù)制構(gòu)造器相比clone方法的優(yōu)勢是簡單,易于實(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 constructorpublic 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)對象的深拷貝。簡略代碼如下

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 + "]"; }}

使用示例兼測試代碼

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對象的child值修改不影響example對象的child值,即使用序列化可以實(shí)現(xiàn)對象的深拷貝。

總結(jié)

以上就是Java中克隆的全部內(nèi)容,希望本文對大家學(xué)習(xí)Java能有所幫助。

發(fā)表評論 共有條評論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 宜宾县| 苏尼特右旗| 葫芦岛市| 化州市| 柘城县| 绥宁县| 双鸭山市| 东城区| 鄂托克旗| 湛江市| 兴宁市| 平舆县| 桐庐县| 扎赉特旗| 太和县| 准格尔旗| 新闻| 通城县| 巴彦淖尔市| 新邵县| 天峻县| 湖口县| 玉龙| 宜兴市| 南岸区| 二手房| 洪洞县| 泗洪县| 南川市| 西吉县| 雷波县| 南陵县| 保定市| 封丘县| 县级市| 泸州市| 且末县| 天等县| 原平市| 普陀区| 喀喇|