首頁 >Java >java教程 >java提高篇(五)-----使用序列化實現物件的拷貝

java提高篇(五)-----使用序列化實現物件的拷貝

黄舟
黄舟原創
2017-02-09 13:39:071170瀏覽

  我們知道在Java中存在這個介面Cloneable,實作該介面的類別都會具備被拷貝的能力,同時拷貝是在記憶體中進行,在效能方面比我們直接透過new產生物件來的快,特別是在大對象的生成上,使得性能的提升非常明顯。然而我們知道拷貝分為深拷貝和淺拷貝之分,但是淺拷貝存在物件屬性拷貝不徹底問題。關於深拷貝、淺拷貝的請參考這裡:漸析java的淺拷貝和深拷貝

       一、淺拷貝問題

        然後將該郵件發給張三、李四、王五三個人,由於他們是使用相同的郵件,並且僅有名字不同,所以使用張三該對象類拷貝李四、王五對象然後更改下名字即可。程式一直到這裡都沒有錯,但是如果我們需要張三提前30分鐘到,即把郵件的內容修改下:

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到二会议室参加会议...

       在這裡同樣是使用張三該對象實現對李四、王五拷貝,最後將張三的郵件內容改為:請與今天12:00到二會議室參加會議...。但結果是:

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());  
    }  
}

這裡我們就疑惑了為什麼李四和王五的郵件內容也發送了改變呢?讓他們提早30分鐘到人家會有意見的!

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

      1、 基本型別

         若變數為基本款很型,則拷貝其值,如int、float等。

      2、 物件

          若變數為實例對象,則拷貝其位址引用,即表示此時新物件與原始物件是公用此實例變數。

      3、 String字串

         若變數為String字串,則拷貝其位址引用。但是在修改時,它會從字串池重新產生一個新的字串,原有紫都城物件保持不變。

      基於上面上面的規則,我們很容易發現問題的所在,他們三者公用一個對象,張三修改了該郵件內容,則李四和王五也會修改,所以才會出現上面的情況。對於這種情況我們還是可以解決的,只需要在clone()方法裡面新建一個對象,然後張三引用該對象即可:

张三的邮件内容是:请与今天12:00到二会议室参加会议...  
李四的邮件内容是:请与今天12:00到二会议室参加会议...  
王五的邮件内容是:请与今天12:00到二会议室参加会议...

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

      對於上面的解決方案還是存在一個問題,若我們系統中存在大量的物件是透過拷貝產生的,如果我們每一個類別都寫一個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;  
    }

      使用該工具類別的物件必須實現Serializable接口,否則是沒有辦法實現複製的。

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介面就可實現物件的克隆,無須繼承Cloneable介面實作clone()方法。

public class Person implements Serializable{  
    private static final long serialVersionUID = 2631590509760908280L;  
  
    ..................  
    //去除clone()方法  
  
}  
  
public class Email implements Serializable{  
    private static final long serialVersionUID = 1267293988171991494L;  
      
    ....................  
}

以上就是 java提高篇(五)-----使用序列化實現物件的拷貝的內容,更多相關內容請關注PHP中文網(www.php.cn)!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn