java提升篇(六)-----使用序列化實現對象的拷貝

      咱們知道在Java中存在這個接口Cloneable,實現該接口的類都會具有被拷貝的能力,同時拷貝是在內存中進行,在性能方面比咱們直接經過new生成對象來的快,特別是在大對象的生成上,使得性能的提高很是明顯。然而咱們知道拷貝分爲深拷貝和淺拷貝之分,可是淺拷貝存在對象屬性拷貝不完全問題。關於深拷貝、淺拷貝的請參考這裏:漸析java的淺拷貝和深拷貝html

1、淺拷貝問題

咱們先看以下代碼:java

public class Person implements Cloneable{
    /** 姓名 **/
    private String name;
    
    /** 電子郵件 **/
    private Email email;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Email getEmail() {
        return email;
    }

    public void setEmail(Email email) {
        this.email = email;
    }
    
    public Person(String name,Email email){
        this.name  = name;
        this.email = email;
    }
    
    public Person(String name){
        this.name = name;
    }

    protected Person clone() {
        Person person = null;
        try {
            person = (Person) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        
        return person;
    }
}

public class Client {
    public static void main(String[] args) {
        //寫封郵件
        Email email = new Email("請參加會議","請與今天12:30到二會議室參加會議...");
        
        Person person1 =  new Person("張三",email);
        
        Person person2 =  person1.clone();
        person2.setName("李四");
        Person person3 =  person1.clone();
        person3.setName("王五");
        
        System.out.println(person1.getName() + "的郵件內容是:" + person1.getEmail().getContent());
        System.out.println(person2.getName() + "的郵件內容是:" + person2.getEmail().getContent());
        System.out.println(person3.getName() + "的郵件內容是:" + person3.getEmail().getContent());
    }
}
--------------------
Output:
張三的郵件內容是:請與今天12:30到二會議室參加會議...
李四的郵件內容是:請與今天12:30到二會議室參加會議...
王五的郵件內容是:請與今天12:30到二會議室參加會議...

      在該應用程序中,首先定義一封郵件,而後將該郵件發給張3、李4、王五三我的,因爲他們是使用相同的郵件,而且僅有名字不一樣,因此使用張三該對象類拷貝李4、王五對象而後更改下名字便可。程序一直到這裏都沒有錯,可是若是咱們須要張三提早30分鐘到,即把郵件的內容修改下:ios

public class Client {
    public static void main(String[] args) {
        //寫封郵件
        Email email = new Email("請參加會議","請與今天12:30到二會議室參加會議...");
        
        Person person1 =  new Person("張三",email);
        
        Person person2 =  person1.clone();
        person2.setName("李四");
        Person person3 =  person1.clone();
        person3.setName("王五");
        
        person1.getEmail().setContent("請與今天12:00到二會議室參加會議...");
        
        System.out.println(person1.getName() + "的郵件內容是:" + person1.getEmail().getContent());
        System.out.println(person2.getName() + "的郵件內容是:" + person2.getEmail().getContent());
        System.out.println(person3.getName() + "的郵件內容是:" + person3.getEmail().getContent());
    }
}

      在這裏一樣是使用張三該對象實現對李4、王五拷貝,最後將張三的郵件內容改變爲:請與今天12:00到二會議室參加會議...。可是結果是:工具

張三的郵件內容是:請與今天12:00到二會議室參加會議...
李四的郵件內容是:請與今天12:00到二會議室參加會議...
王五的郵件內容是:請與今天12:00到二會議室參加會議...

      這裏咱們就疑惑了爲何李四和王五的郵件內容也發送了改變呢?讓他們提早30分鐘到人家會有意見的!性能

      其實出現問題的關鍵就在於clone()方法上,咱們知道該clone()方法是使用Object類的clone()方法,可是該方法存在一個缺陷,它並不會將對象的全部屬性所有拷貝過來,而是有選擇性的拷貝,基本規則以下:this

      一、 基本類型spa

         若是變量是基本很類型,則拷貝其值,好比int、float等。code

      二、 對象htm

         若是變量是一個實例對象,則拷貝其地址引用,也就是說此時新對象與原來對象是公用該實例變量。對象

      三、 String字符串

         若變量爲String字符串,則拷貝其地址引用。可是在修改時,它會從字符串池中從新生成一個新的字符串,原有紫都城對象保持不變。

      基於上面上面的規則,咱們很容易發現問題的所在,他們三者公用一個對象,張三修改了該郵件內容,則李四和王五也會修改,因此纔會出現上面的狀況。對於這種狀況咱們仍是能夠解決的,只須要在clone()方法裏面新建一個對象,而後張三引用該對象便可:

protected Person clone() {
        Person person = null;
        try {
            person = (Person) super.clone();
            person.setEmail(new Email(person.getEmail().getObject(),person.getEmail().getContent()));
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        
        return person;
    }

      因此:淺拷貝只是Java提供的一種簡單的拷貝機制,不便於直接使用。

      對於上面的解決方案仍是存在一個問題,若咱們系統中存在大量的對象是經過拷貝生成的,若是咱們每個類都寫一個clone()方法,並將還須要進行深拷貝,新建大量的對象,這個工程是很是大的,這裏咱們能夠利用序列化來實現對象的拷貝。

2、利用序列化實現對象的拷貝

      如何利用序列化來完成對象的拷貝呢?在內存中經過字節流的拷貝是比較容易實現的。把母對象寫入到一個字節流中,再從字節流中將其讀出來,這樣就能夠建立一個新的對象了,而且該新對象與母對象之間並不存在引用共享的問題,真正實現對象的深拷貝。

public class CloneUtils {
    @SuppressWarnings("unchecked")
    public static <T extends Serializable> T clone(T obj){
        T cloneObj = null;
        try {
            //寫入字節流
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            ObjectOutputStream obs = new ObjectOutputStream(out);
            obs.writeObject(obj);
            obs.close();
            
            //分配內存,寫入原始對象,生成新對象
            ByteArrayInputStream ios = new ByteArrayInputStream(out.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(ios);
            //返回生成的新對象
            cloneObj = (T) ois.readObject();
            ois.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return cloneObj;
    }
}

      使用該工具類的對象必需要實現Serializable接口,不然是沒有辦法實現克隆的。

public class Person implements Serializable{
    private static final long serialVersionUID = 2631590509760908280L;

    ..................
    //去除clone()方法

}

public class Email implements Serializable{
    private static final long serialVersionUID = 1267293988171991494L;
    
    ....................
}

      因此使用該工具類的對象只要實現Serializable接口就可實現對象的克隆,無須繼承Cloneable接口實現clone()方法。

public class Client {
    public static void main(String[] args) {
        //寫封郵件
        Email email = new Email("請參加會議","請與今天12:30到二會議室參加會議...");
        
        Person person1 =  new Person("張三",email);
        
        Person person2 =  CloneUtils.clone(person1);
        person2.setName("李四");
        Person person3 =  CloneUtils.clone(person1);
        person3.setName("王五");
        person1.getEmail().setContent("請與今天12:00到二會議室參加會議...");
        
        System.out.println(person1.getName() + "的郵件內容是:" + person1.getEmail().getContent());
        System.out.println(person2.getName() + "的郵件內容是:" + person2.getEmail().getContent());
        System.out.println(person3.getName() + "的郵件內容是:" + person3.getEmail().getContent());
    }
}
-------------------
Output:
張三的郵件內容是:請與今天12:00到二會議室參加會議...
李四的郵件內容是:請與今天12:30到二會議室參加會議...
王五的郵件內容是:請與今天12:30到二會議室參加會議...

鞏固基礎,提升技術,不懼困難,攀登高峯!!!!!!

參考文獻《編寫高質量代碼 改善Java程序的151個建議》----秦小波

相關文章
相關標籤/搜索