1. 程式人生 > 實用技巧 >Java拷貝-深拷貝與淺拷貝

Java拷貝-深拷貝與淺拷貝

一、出現原因

在專案中經常需要複製一個完全一樣的物件,然後再對新物件進行更新等操作而不影響老物件。

而以以下方式獲取是否會出現問題呢?

User user = new User();
User copyUser = user;

答案是肯定的,上面的方法不能稱之為複製物件,更準確地說應該是複製引用,因為user和copyUser指向的是記憶體堆裡的同一個物件:

user ¦→ Object

copyUser ¦→ ↑

棧區(引用) ¦ 堆區(物件)

那麼如何才能實現呢?應該使用Java中的拷貝(Object Copy),主要分為:淺拷貝 (Shallow Copy)、深拷貝 (Deep Copy),用的方法為clone()。

二、淺拷貝與深拷貝的區別

1、淺拷貝:

類實現預設的Object.clone()方法,拷貝物件時,

(1)對於引用型別的成員變數(屬性)拷貝只是拷貝“值”即地址(引用),沒有在堆中開闢新的記憶體空間;

(2)對於欄位型別是值型別(基本型別)的,那麼對該欄位進行復制。

2、深拷貝:

類重寫clone()方法,對於引用型別成員變數,重新在堆中開闢新的記憶體空間,簡單地說,將物件的全部屬性內容都拷貝一份新的。

3、使用:

如果複製物件只含基本資料型別,則使用淺拷貝即可滿足;若果包含成員物件,則需使用深拷貝。

三、淺拷貝

一般實現方法:

  1. 被複制的類需要實現 Clonenable 介面(不實現的話在呼叫 clone 方法會丟擲 CloneNotSupportedException 異常) 該介面為標記介面 (不含任何方法)

  2. 覆蓋 clone () 方法,方法中呼叫 super.clone () 方法得到需要的複製物件,(native 為本地方法)

public class Person implements Cloneable{
    private  String name;
    private  int age;
    private Address address;

    //構造方法
    public Person(String name, int age, Address address) {
        this.name = name;
        this.age = age;
        
this.address = address; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } public String display(){ return "Person[name="+name+",age="+age+",address"+address+"]"; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public Address getAddress() { return address; } public void setAddress(Address address) { this.address = address; } } public class Address { private String province;//省份 private String city;//所在城市 //構造方法 public Address(String province, String city) { this.province = province; this.city = city; } public String getProvince() { return province; } public void setProvince(String province) { this.province = province; } public String getCity() { return city; } public void setCity(String city) { this.city = city; } @Override public String toString() { return "Address[province="+province+",city="+city+"]"; } } public class Test { public static void main(String[] args) throws CloneNotSupportedException { Person person=new Person("張三",20,new Address("安徽","合肥")); Person clonePerson=(Person) person.clone(); System.out.println(person); //com.hand.hand.clone.Person@1d81eb93 System.out.println(clonePerson); //com.hand.hand.clone.Person@1d81eb93 System.out.println(person.display()); //Person[name=張三,age=20,addressAdderss[provice=安徽,city=合肥]] System.out.println(clonePerson.display()); //Person[name=張三,age=20,addressAdderss[provice=安徽,city=合肥]]

     //證明String的特殊性,因為String是final的,所以會開闢新的記憶體空間 clonePerson.setName("李四");
     //證明基本資料型別是被拷貝是開闢新空間的 clonePerson.setAge(
22);
     //證明成員變數所指向的是同一個物件 Address address
=clonePerson.getAddress(); address.setProvince("江蘇"); System.out.println(person.display()); //Person[name=張三,age=20,addressAdderss[provice=江蘇,city=合肥]] System.out.println(clonePerson.display()); //Person[name=李四,age=22,addressAdderss[provice=江蘇,city=合肥]] } }

四、深拷貝

針對以上成員變數無法完成拷貝的情況出現了深拷貝。

實現方式有以下兩種:

  • 第一種

與通過重寫 clone 方法實現淺拷貝的基本思路一樣,只需要為物件圖的每一層的每一個物件都實現 Cloneable 介面並重寫 clone 方法,最後在最頂層的類的重寫的 clone 方法中呼叫所有的 clone 方法即可實現深拷貝。簡單的說就是:每一層的每個物件都進行淺拷貝 = 深拷貝。

  • 第二種

結合序列化來解決這個問題,先把物件序列化,然後再反序列化成物件,該物件保證每個引用都是嶄新的。這個就形成了多個引用,原引用和反序列化之後的引用不在相同,具體實現:

public class Student implements Cloneable, Serializable {
    private static final long serialVersionUID = 1L;

    private int age;
    private Address address;

    public void setAddress(Address address) {
        this.address = address;
    }

    public Address getAddress() {
        return address;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public int getAge() {
        return age;
    }

    @Override
    protected Object clone() {
        Student stu = null;
        try {
            // 將物件寫成byte array
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(this);

            // 從流中讀出byte array
            ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bis);
            stu = (Student) ois.readObject();
        } catch (Exception e) {
            e.printStackTrace();
        }

        return stu;
    }
}



public class Test {
    public static void main(String[] args) {
        Student stu1 = new Student();
        Address address = new Address();
        address.setAddress("beijing");
        stu1.setAge(20);
        stu1.setAddress(address);
        Student stu2 = (Student) stu1.clone();

        address.setAddress("jinan");
        stu2.setAge(10);

        System.out.println(stu1.getAge() + stu1.getAddress().getAddress());
        //20jinan
        System.out.println(stu2.getAge() + stu2.getAddress().getAddress());
        //10beijing

        stu2.setAddress(address);
        System.out.println(stu1.getAge() + stu1.getAddress().getAddress());
        //20jinan
        System.out.println(stu2.getAge() + stu2.getAddress().getAddress());
        //10jinan
    }
}