欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

深入理解Java中的克隆

 更新時間:2016年08月15日 15:47:44   投稿:daisy  
想必大家對克隆都有耳聞,世界上第一只克隆羊多莉就是利用細胞核移植技術將哺乳動物的成年體細胞培育出新個體,甚為神奇。其實在Java中也存在克隆的概念,即實現對象的復制。本文將嘗試介紹一些關于Java中的克隆和一些深入的問題,希望可以幫助大家更好地了解克隆。

前言

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

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

      1、被克隆的類必須自己實現Cloneable 接口,以指示 Object.clone() 方法可以合法地對該類實例進行按字段復制。Cloneable 接口實際上是個標識接口,沒有任何接口方法。

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

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

        protected Object clone()
        throws CloneNotSupportedException

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

按照慣例,返回的對象應該通過調用 super.clone 獲得。

Java中的賦值

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

//原始類型
int a = 1;
int b = a;

//引用類型
String[] weekdays = new String[5];
String[] gongzuori = weekdays;//僅拷貝引用

在上述代碼中。

      1、如果是原始數據類型,賦值傳遞的為真實的值

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

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

Clone

在Java中,clone是將已有對象在內存中復制出另一個與之相同的對象的過程。java中的克隆為逐域復制。

在Java中想要支持clone方法,需要首先實現Cloneable接口

Cloneable其實是有點奇怪的,它不同與我們常用到的接口,它內部不包含任何方法,它僅僅是一個標記接口。

其源碼如下

public interface Cloneable {
}

關于cloneable,需要注意的

      1、如果想要支持clone,就需要實現Cloneable 接口

      2、如果沒有實現Cloneable接口的調用clone方法,會拋出CloneNotSupportedException異常。

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

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

調用clone方法復制對象

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、執(zhí)行快,效率高

     5、不能做到數據的100%分離。

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

關于無法做到數據分離,我們可以使用這段代碼驗證

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 得到的結果是

main imp1.child.name=Bob

原因是淺拷貝并沒有做到數據的100%分離,imp1和imp2共享同一個Child對象,所以一個修改會影響到另一個。

深拷貝

深拷貝可以解決數據100%分離的問題。只需要對上面代碼進行一些修改即可。

1、Child實現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方法,調用數據域的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;
 }
}

當我們再次修改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方法創(chuàng)建并返回該對象的一個拷貝。而拷貝的精確含義取決于該對象的類。一般的含義是,對于任何對象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方法的優(yō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;
 }
}

注意,上面的代碼實現為淺拷貝,如果想要實現深拷貝,參考如下代碼

//copy constructor
public Car(Car car) {
 Wheel wheel = new Wheel();
 wheel.brand = car.wheel.brand;
   
 this.wheel = wheel;
 this.manufacturer = car.manufacturer;
}

為了更加便捷,我們還可以為上述類增加一個靜態(tài)的方法

public static Car newInstance(Car car) {
 return new Car(car);
}

使用Serializable實現深拷貝

其實,使用序列化也可以實現對象的深拷貝。簡略代碼如下

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接口

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);
}
//輸出結果:example.child=Child [name=Example];copy.child=Child [name=Copied]

由輸出結果來看,copy對象的child值修改不影響example對象的child值,即使用序列化可以實現對象的深拷貝。

總結

以上就是Java中克隆的全部內容,希望本文對大家學習Java能有所幫助。

相關文章

  • SpringCloud之Zuul網關原理及其配置講解

    SpringCloud之Zuul網關原理及其配置講解

    這篇文章主要介紹了SpringCloud之Zuul網關原理及其配置講解,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-03-03
  • Mysql字段和java實體類屬性類型匹配方式

    Mysql字段和java實體類屬性類型匹配方式

    這篇文章主要介紹了Mysql字段和java實體類屬性類型匹配方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • Spring Boot FeignClient 如何捕獲業(yè)務異常信息

    Spring Boot FeignClient 如何捕獲業(yè)務異常信息

    這篇文章主要介紹了Spring Boot FeignClient 如何捕獲業(yè)務異常信息的操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-06-06
  • Spring cloud Eureka注冊中心搭建的方法

    Spring cloud Eureka注冊中心搭建的方法

    這篇文章主要介紹了Spring cloud Eureka注冊中心搭建的方法,小編覺得挺不錯的,現在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-04-04
  • Java 8 Function函數式接口及函數式接口實例

    Java 8 Function函數式接口及函數式接口實例

    函數式接口(Functional Interface)就是一個有且僅有一個抽象方法,但是可以有多個非抽象方法的接口。接下來通過本文給大家介紹Java 8 Function函數式接口及函數式接口實例代碼,需要的朋友可以參考下
    2018-05-05
  • Springboot項目升級2.2.x升至2.7.x的示例代碼

    Springboot項目升級2.2.x升至2.7.x的示例代碼

    本文主要介紹了Springboot項目升級2.2.x升至2.7.x的示例代碼,會有很多的坑,具有一定的參考價值,感興趣的可以了解一下
    2023-09-09
  • Java_Spring之XML?的?AOP?配置

    Java_Spring之XML?的?AOP?配置

    這篇文章主要介紹了Java_Spring中基于XML的AOP配置,上篇講到的是基于注解的AOP配置,對XML感興趣的同學可以參考閱讀本文
    2023-04-04
  • Java設計模式之橋接模式的示例詳解

    Java設計模式之橋接模式的示例詳解

    橋梁模式是對象的結構模式。又稱為柄體(Handle and Body)模式或接口(Interface)模式。本文將通過示例來詳細講解一下這個模式,感興趣的可以學習一下
    2022-02-02
  • 詳解Java如何實現自定義注解

    詳解Java如何實現自定義注解

    注解(Annotation),也叫元數據。一種代碼級別的說明。它是JDK1.5及以后版本引入的一個特性,與類、接口、枚舉是在同一個層次。本文將通過示例詳解Java如何實現自定義注解,需要的可以參考一下
    2022-06-06
  • Spring Boot2.3 新特性分層JAR的使用

    Spring Boot2.3 新特性分層JAR的使用

    這篇文章主要介紹了Spring Boot2.3 新特性分層JAR的使用,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-06-06

最新評論