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、使用:
如果複製物件只含基本資料型別,則使用淺拷貝即可滿足;若果包含成員物件,則需使用深拷貝。
三、淺拷貝
一般實現方法:
-
被複制的類需要實現 Clonenable 介面(不實現的話在呼叫 clone 方法會丟擲 CloneNotSupportedException 異常) 該介面為標記介面 (不含任何方法)
-
覆蓋 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 } }