Java高級特性:clone()方法

標籤:ringbuf   his   硬件   throws   port   protect   序列化   ext   this   

目錄

源碼

public class Objcet{
    protected native Object clone() throws CloneNotSupportedException();
}

由源碼可知。

  • 第一:Objcet類的clone()方法是一個native方法。native方法的執行效率一般遠高於Java中的非native方法(一般不是java語言所寫)。這也解釋了爲什麼要用Object的clone()方法,而不是先new一個類,然後把原始對象複製到新對象中,雖然這樣也能實現clone功能。(JNI是Java Native Interface的 縮寫。從Java 1.1開始,Java Native Interface (JNI)標準成爲java平臺的一部分,它允許Java代碼和其他語言寫的代碼進行交互。JNI一開始是爲了本地已編譯語言,尤其是C和C++而設計的,但是它並不妨礙你使用其他語言,只要調用約定受支持就可以了。使用java與本地已編譯的代碼交互,通常會喪失平臺可移植性。但是,有些情況下這樣做是可以接受的,甚至是必須的,比如,使用一些舊的庫,與硬件、操作系統進行交互,或者爲了提高程序的性能。JNI標準至少保證本地代碼能工作在任何Java 虛擬機實現下。)
  • 第二:Object類中的clone()方法被protected修飾符修飾。(關於protected修飾符見我的其它文章。)這意味着clone()方法只對java.util.lang包可見,和繼承了Object類的子類可見。當然所有類都是繼承了Object類。
    • 爲什麼要用protected修飾呢?
      爲了安全,安全性從兩方面考慮。首先我們要clone一個Employee對象,應該只有Employee類能夠克隆Employee對象。使用protected修飾符保證了其它不相關的類無法克隆Employee對象。其次是Object對要複製的對象一無所知,它只會逐域進行復制,如果對象中的所有數據域都是數值或其他基本類型,拷貝這些域沒有任何問題。但是如果對象包含子對象的引用,拷貝域就會得到相同子對象的另一個引用,這樣原對象和克隆的對象還會共享一些信息。所以默認的拷貝都是淺拷貝。要想在任何地方都使用clone方法,我們必須將它改爲public類型,並且實現Cloneable接口。如果一開始就是public,我們就分不清到底是淺拷貝還是深拷貝了。使用protected和Cloneable就約束了類設計者要了解自己的克隆過程。
      Cloneable接口是標記接口,它不包含任何需要實現的方法。如果一個對象請求克隆,但沒有實現這個接口,就會產生一個受檢查異常,因爲clone方法默認實現中有使用instanceof進行接口判斷。
      這裏給出代碼
//cloneTest.java
package com.testbase.clone;

public class cloneTest {
    public static void main(String[] args){
        // 雖然我們已經實現了Cloneable接口,不會產生異常
        // 但是編譯器並不知道,會報錯,所以這裏要捕獲異常
        try
        {
            Employee tobin = new Employee(30000);
            int salary = tobin.getSalary();
            System.out.println(salary);
            Employee shengsheng = tobin.clone();
            int shengSalary = shengsheng.getSalary();
            System.out.println(shengSalary);
        }
        catch (CloneNotSupportedException e)
        {
            e.printStackTrace();
        }
    }
}
//Employee.java
package com.testbase.clone;

public class Employee implements Cloneable
{
    private int salary;
    public Employee()
    {

    }
    public Employee(int asalary)
    {
        salary = asalary;
    }
    @Override
    public Employee clone() throws CloneNotSupportedException
    {
        Employee cloned = (Employee) super.clone();
        return cloned;
    }

    public int getSalary() {
        return salary;
    }
}
  • 第三:Object.clone()方法返回一個Object對象。我們必須進行強制轉換才能得到我們需要的類型。(強制轉換一定能成功嗎?如果重寫了clone方法一定成功。但是如果沒有重寫,不能轉換。clone的是父類Object,無法向下造型,子類重寫了clone方法,拷貝就是當前類的對象,暫時轉爲Object,還可以通過強制類型轉換回來)
    給出一段錯誤代碼。在Employee.java中拷貝是因爲在其它類中拷貝一定不成功。因爲Object類clone方法的protected特性。只有繼承了它的子類和java.util.lang包可以調用clone()方法。所以選擇在子類Employee中測試。
package com.testbase.clone;

public class Employee implements Cloneable
{
    private int salary;
    public Employee()
    {

    }
    public Employee(int asalary)
    {
        salary = asalary;
    }
//    @Override
//    public Employee clone() throws CloneNotSupportedException
//    {
//        Employee cloned = (Employee) super.clone();
//        return cloned;
//    }

    public int getSalary() {
        return salary;
    }

    public static void main(String[] args){
        try
        {
            Employee tobin = new Employee(30000);
            int salary = tobin.getSalary();
            System.out.println(salary);
            Object shengsheng = tobin.clone(); //這裏不能強制類型轉換
            int shengSalary = shengsheng.getSalary();//這裏報錯,因爲Object沒有getSalary方法
            System.out.println(shengSalary);
        }
        catch (CloneNotSupportedException e)
        {
            e.printStackTrace();
        }
    }
}

深拷貝和淺拷貝

淺拷貝:拷貝引用,但是不拷貝引用指向的對象,對拷貝引用的對象進行修改,兩份拷貝都會被修改。如果源對象和淺拷貝對象所共享的子對象都是不可變的,那麼這種共享就是安全的。比如String類。或者在對象的生命週期內,子對象一直包含着不變的常量,沒有更改器方法會改變它,也沒有方法會生成它的引用,這種情況同樣是安全的。
深拷貝:可能會更改的子對象也進行了拷貝。要進行深拷貝,需要一層層地重寫clone方法。

  • 數組是深拷貝。所有數組類型都有一個public的clone()方法,可以用這個方法建立一個新數組。
int[] a = {1,2,3,4};
int[] cloned = a.clone();
cloned[0]=11;

對象串行化實現拷貝

常見面試題

1.爲什麼進行拷貝

  • 因爲我們某個對象實例現在需要保存一些有效值,我們希望生成一個和原來一樣的對象,對這個對象的修改不改變原對象的屬性。

2.深拷貝和淺拷貝的區別

  • 淺拷貝對基本數據類型生成一份新的拷貝,對引用類型,只是複製了引用,引用所指向的那塊內存空間並沒有拷貝
  • 深拷貝對引用指向的那塊內存空間也拷貝了一份(新內存空間)。換言之深拷貝要把複製的對象所引用的對象也都拷貝了一遍。

3.String克隆的特殊性在那裏?StringBuffer和StringBuilder呢?

  • 對基本數據類型都能自動實現深拷貝。而對引用類型是淺拷貝。String是引用類型的特例。因爲String是不允許修改的。所以相當於進行了深拷貝,是安全的。由於String是不可變類,對String類中的很多修改操作都是通過new對象複製處理的。所以當我們修改clone前後對象裏面String屬性的值時,實際上就指向了新的內存空間。自然對clone前的源對象沒有影響,類似於深拷貝。雖然它是引用類型,但是不影響我們深拷貝的使用。
    而對於StringBuffer和StringBuilder,需要主動進行clone重寫。否則就是淺拷貝。

4.實現對象克隆的常見方式有哪些,具體怎麼做?
常用的方式有三種。

  • 通過自己寫一個clone方法,new一個同樣的對象,賦值實現深度克隆,繁瑣容易出錯。
  • 通過實現Cloneable接口並重寫Object類的clone方法,分爲深淺兩種方式。
  • 通過Serializable接口並用對象的序列化和反序列化來實現真正的深拷貝。

代碼,主要是第2和第3個方法。
通過實現 Cloneable 接口並重寫 Object 類的 clone() 方法實現淺克隆做法:Object 類中 clone 方法的默認實現最終是一個 native 方法(如果 clone 類沒有實現 Cloneable 接口並調用了 Object 的 clone 方法就會拋出 CloneNotSupportedException 異常,因爲 clone 方法默認實現中有使用 instanceof 進行接口判斷),相對來說效率高些,默認實現是先在內存中開闢一塊和原始對象一樣的空間,然後原樣拷貝原始對象中的內容,對基本數據類型就是值複製,而對非基本類型變量保存的僅僅是對象的引用,所以會導致 clone 後的非基本類型變量和原始對象中相應的變量指向的是同一個對象。
淺拷貝。

class Employee implements Clonealbe{
    public Employee clone() throws CloneNotSupportedException
    {
        return (Employee) super.clone();
    }        
}

深拷貝

class Employee implements Clonealbe{
    public Employee clone() throws CloneNotSupportedException
    {
        Employee cloned = (Employee) super.clone();
        cloned.hireDay = (Date) hireDay.clone();
        return cloned();
    }        
}

通過Serializable接口並用對象的序列化和反序列化來實現真正的深拷貝。(還未學到)

class CloneUtil {  
   public static <T extends Serializable> T clone(T obj) {  
       T cloneObj = null;
       try {
           ByteArrayOutputStream byteOut = new ByteArrayOutputStream();  
           ObjectOutputStream objOut = new ObjectOutputStream(byteOut);
           objOut.writeObject(obj);  
           objOut.close();  
           ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray()); 
           ObjectInputStream objIn = new ObjectInputStream(byteIn);  
           cloneObj = (T) objIn.readObject();  
           objIn.close();
       } catch (IOException e) {
           e.printStackTrace();
       } catch (ClassNotFoundException e) {
           e.printStackTrace();
       }  
       return cloneObj;  
   }
}
 
 
class InfoBean implements Serializable {  
   public String name;
   public int age;
}
 
 
class PeopleBean implements Serializable {  
   public String vipId;
   public InfoBean infoBean;
 
   public Object clone() {
       return CloneUtil.clone(this);
   }  
}

參考文章:
https://www.cnblogs.com/gw811/archive/2012/10/07/2712252.html
https://blog.csdn.net/qq_26857649/article/details/84316081

Java高級特性:clone()方法

標籤:ringbuf   his   硬件   throws   port   protect   序列化   ext   this   

原文地址:https://www.cnblogs.com/zuotongbin/p/11723346.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章