在阿里Java開發手冊中,有這麼一條建議:慎用 Object 的 clone 方法來拷貝對象。對象 clone 方法默認是淺拷貝,若想實現深拷貝需覆寫 clone 方法實現域對象的深度遍歷式拷貝 。Java中的對象拷貝,有淺拷貝和深拷貝兩種,若是沒有搞清楚這二者的區別,那麼可能會給本身的代碼埋下隱患。java
淺拷貝:被複制對象的全部變量都含有與原來的對象相同的值,而全部的對其餘對象的引用仍然指向原來的對象。換言之,淺複製僅僅複製所考慮的對象,而不復制它所引用的對象。apache
深拷貝:被複制對象的全部變量都含有與原來的對象相同的值,除去那些引用其餘對象的變量。那些引用其餘對象的變量將指向被複制過的新對象,而再也不是原有的那些被引用的對象。換言之,深複製把要複製的對象所引用的對象都複製了一遍。bash
經過上面的結論,咱們能夠看出淺拷貝和深拷貝的區別就在於所要拷貝的對象的引用數據類型,若是是拷貝一份引用,那麼這是淺拷貝,若是是新建一個對象,那麼這就是深拷貝。markdown
在Java的Object對象中,有clone這個方法。它被聲明爲了 protected
,因此咱們能夠在其子類中使用它。這裏須要注意的是,咱們在子類中使用clone方法時,子類須要實現Cloneable接口,不然會拋出java.lang.CloneNotSupportedException異常。app
以下實體類都使用了Lombok。ide
Address.java工具
@Data public class Address { private String address; } 複製代碼
Person.javaoop
@Data public class Person implements Cloneable { private String name; private Integer age; private Address address; @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } } 複製代碼
淺拷貝,示例代碼以下:測試
public static void main(String[] args) throws CloneNotSupportedException { Person person = new Person(); person.setName("Happyjava"); person.setAge(33); Address address = new Address(); address.setAddress("浙江杭州"); person.setAddress(address); Person newPerson = (Person) person.clone(); System.out.println(person == newPerson); System.out.println(person.getAddress() == newPerson.getAddress()); } 複製代碼
經過 == 比較是不是同一個對象。其運行結果以下:this
false true 複製代碼
說明了經過clone方法拷貝出來的對象,與原對象並非同一個對象。而person.getAddress() == newPerson.getAddress() 的比較是true,說明了兩者的引用都是指向同一個對象。這就是淺拷貝,引用類型仍是指向原來的對象。
不少時候,咱們拷貝一個對象,是但願徹底進行深度拷貝的。淺拷貝存在的問題就是,對於原對象引用類型的屬性進行修改,拷貝出來的對象也會受到影響(由於兩者的引用都指向同一個對象)。以下代碼:
public static void main(String[] args) throws CloneNotSupportedException { Person person = new Person(); person.setName("Happyjava"); person.setAge(33); Address address = new Address(); address.setAddress("浙江杭州"); person.setAddress(address); Person newPerson = (Person) person.clone(); newPerson.getAddress().setAddress("廣東省深圳市"); System.out.println(person.getAddress().getAddress()); } 複製代碼
運行結果以下:
經過newPerson把address設置爲「廣東省深圳市」,person的address也變成了"廣東省深圳市"。
這種狀況,若是咱們沒有注意,是很容易形成生產事故的。
經過clone方法實現深拷貝,是一件比較麻煩的事情,由於咱們須要手動在clone方法裏拷貝引用類型。代碼修改以下:
Address.java
@Data public class Address implements Cloneable { private String address; @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } } 複製代碼
Person.java
@Data public class Person implements Cloneable { private String name; private Integer age; private Address address; @Override protected Object clone() throws CloneNotSupportedException { Person newPerson = (Person) super.clone(); newPerson.address = (Address) this.address.clone(); return newPerson; } } 複製代碼
經過clone方法實現深拷貝,咱們須要在Person的clone方法裏調用address的clone方法,而且手動設置clone出來的新的address。
再次執行上面的測試代碼,運行結果以下:
經過clone方法實現深拷貝是比較麻煩的一件事情,這裏推薦你們能夠經過序列化、反序列化的方式實現深拷貝。咱們能夠直接使用commons-lang3包的序列化、反序列工具類。
引入依賴
<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.8.1</version> </dependency> 複製代碼
序列化須要實現Serializable接口,Person和Address類都須要實現。測試代碼以下:
public static void main(String[] args) throws CloneNotSupportedException { Person person = new Person(); person.setName("Happyjava"); person.setAge(33); Address address = new Address(); address.setAddress("浙江杭州"); person.setAddress(address); // 序列化 byte[] serialize = SerializationUtils.serialize(person); // 反序列化 Person newPerson = SerializationUtils.deserialize(serialize); System.out.println(person == newPerson); System.out.println(person.getAddress() == newPerson.getAddress()); } 複製代碼
運行結果以下:
經過結果能夠看出,反序列化構建出來的對象,是全新的、深度拷貝的。
拷貝對象,若是直接經過clone方法進行拷貝,是很容易出現問題的。咱們要清楚的知道淺拷貝和深拷貝的區別。
本文發佈於掘金號【Happyjava】。Happy的掘金地址:juejin.cn/user/398428…,Happy的我的博客:blog.happyjava.cn。歡迎轉載,但須保留此段聲明。