Java 深拷貝與淺拷貝的分析
在正式的進(jìn)入主題之前,我們先來了解下深拷貝和前拷貝的概念:
淺拷貝:
會創(chuàng)建一個新對象,這個對象有著原始對象屬性值的一份精確拷貝,如果屬性是基本類型,拷貝的是基本類型的值;如果屬性是內(nèi)存地址,拷貝的就是內(nèi)存地址,因此如果一個對象改變了這個地址就會影響到另一個對象;
深拷貝:
不僅要復(fù)制對象的所有非引用成員變量值,還要為引用類型的成員變量創(chuàng)建新的實(shí)例,并且初始化為形式參數(shù)實(shí)例值;
了解完概念之后,我們來測試下普通的對象賦值操作屬于深拷貝還是淺拷貝:
測試代碼:
public class DepthCopy { public static void main(String[] args) { Copy first = new Copy("hzw", 24); Copy second = first; second.name = "shanxi"; System.out.println(first.name);//輸出shanxi } } class Copy { public String name; public int age; public Copy(String name,int age) { this.name = name; this.age = age; } }
可以發(fā)現(xiàn),在second將name屬性值修改為shanxi之后,first的name屬性值也變成了shanxi,這點(diǎn)就可以看出普通的對象賦值屬于淺拷貝;
明白了對象之間賦值是淺拷貝之后,接下來我們來看看克隆到底是深拷貝還是淺拷貝,測試代碼是讓上面的Copy對象實(shí)現(xiàn)Cloneable接口里面的clone方法:
public class DepthCopy { public static void main(String[] args) { Copy first = new Copy("hzw", 24); Copy second = null; try { second = (Copy) first.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } second.name = "shanxi"; System.out.println(first.name);//輸出: hzw System.out.println(first);//輸出: com.hzw.day33.Copy@7f39ebdb System.out.println(second);//輸出: com.hzw.day33.Copy@33abb81e } } class Copy implements Cloneable { public String name; public int age; public Copy(String name,int age) { this.name = name; this.age = age; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } }
可以看出原先創(chuàng)建出的對象first和克隆創(chuàng)建出的對象second是兩個實(shí)例,因此對于second中name屬性的修改并不會影響first中的name屬性;但是,我們并不能單純的認(rèn)為克隆就是深拷貝的,比如下面這個例子:
public class DepthCopy { public static void main(String[] args) { Student student = new Student(95); Copy first = new Copy("hzw", 24,student); Copy second = null; try { second = (Copy) first.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } second.name = "shanxi"; second.student.score = 60; System.out.println(first == second);//false System.out.println(first.student == second.student);//true System.out.println(first.student.score);//60 } } class Copy implements Cloneable { public String name; public int age; public Student student; public Copy(String name,int age,Student student) { this.name = name; this.age = age; this.student = student; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } } class Student { public int score; public Student(int score) { this.score = score; } }
看到?jīng)]有呢?我們通過克隆的方式創(chuàng)建了second,很明顯發(fā)現(xiàn)first和second是兩個實(shí)例,因為first == second輸出為false,但是first和second里面的student對象卻是一樣的,通過second修改了student的score值之后,first里面student的score也發(fā)生了改變,這也就是說first和second里面的student是相同的,這也就說明了克隆是淺拷貝的,我們要想實(shí)現(xiàn)克隆的深拷貝,必須讓Copy對象里面的Student對象也要實(shí)現(xiàn)Cloneable接口里面的clone方法,并且在Copy里面的克隆方法返回Student的一個克隆即可,這樣就可以保證Student的唯一啦,修改之后的代碼如下:
public class DepthCopy { public static void main(String[] args) { Student student = new Student(95); Copy first = new Copy("hzw", 24,student); Copy second = null; try { second = (Copy) first.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } second.name = "shanxi"; second.student.score = 60; System.out.println(first == second);//false System.out.println(first.student == second.student);//false System.out.println(first.student.score);//95 System.out.println(second.student.score);//60 } } class Copy implements Cloneable { public String name; public int age; public Student student; public Copy(String name,int age,Student student) { this.name = name; this.age = age; this.student = student; } @Override protected Object clone() throws CloneNotSupportedException { Copy copy = (Copy)super.clone(); copy.student = (Student) student.clone(); return copy; } } class Student implements Cloneable { public int score; public Student(int score) { this.score = score; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } }
可以看到此時first和second和first.student和second.student都不是相同的,因此我們修改second的student的score之后并沒有影響到first里的student的score值,達(dá)到了深拷貝的目的;
但是,仔細(xì)一想問題就出來了,假如我們上面例子的Student類中也存在引用類型的屬性,比如College類,那么我們必須讓College類實(shí)現(xiàn)Cloneable接口,然后在Student類里面的clone方法里面調(diào)用College類的clone方法,在Copy類的clone方法中調(diào)用Student類的clone方法,發(fā)現(xiàn)沒有了,這個過程好復(fù)雜,必須讓類中的有關(guān)引用類型全部實(shí)現(xiàn)Cloneable接口,感覺好麻煩是不是,好的,接下來就該牛人登場了;
解決深拷貝問題最好的方式就是采用序列化方式,這樣各種類均不用實(shí)現(xiàn)Cloneable接口的,直接序列化反序列化就可以啦,我們來見識下吧。
import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; public class DepthCopy { public static void main(String[] args) { College school = new College("nongda"); Student student = new Student(95, school); Copy copy = new Copy("hzw",23, student); Copy another = null;//表示反序列化出來的類實(shí)例 //進(jìn)行序列化操作 try { FileOutputStream fos = new FileOutputStream(new File("d:/copy.txt")); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(copy); } catch (Exception e) { e.printStackTrace(); } //進(jìn)行反序列化操作 FileInputStream fis; try { fis = new FileInputStream(new File("d:/copy.txt")); ObjectInputStream ois = new ObjectInputStream(fis); another = (Copy) ois.readObject(); } catch (Exception e) { e.printStackTrace(); } System.out.println(copy == another);//false System.out.println(copy.student == another.student);//false System.out.println(copy.student.school == another.student.school);//false another.student.school.schoolName = "wuda"; System.out.println(copy.student.school.schoolName);//nongda } } class Copy implements Serializable { public String name; public int age; public Student student; public Copy(String name,int age,Student student) { this.name = name; this.age = age; this.student = student; } } class Student implements Serializable { public int score; public College school; public Student(int score,College school) { this.score = score; this.school = school; } } class College implements Serializable { public String schoolName; public College(String schoolName) { this.schoolName = schoolName; } }
從輸出就可以看出來,反序列化之后生成的對象完全就是對原對象的一份拷貝,除了屬性值相同之外并不和原對象有任何關(guān)系,因此當(dāng)我們修改反序列化生成對象的schoolName為"wuda"的時候并沒有修改原來實(shí)例的schoolName值,還是輸出"nongda",因此達(dá)到了真正的深拷貝效果,但是要想實(shí)現(xiàn)序列化,所有的有關(guān)類都必須實(shí)現(xiàn)Serializable接口,這總也比既實(shí)現(xiàn)Cloneable接口又實(shí)現(xiàn)clone方法更方便吧。
以上就是對Java 深拷貝和淺拷貝的詳細(xì)講解,有需要的可以參考下。
相關(guān)文章
Java微信公眾平臺開發(fā)(4) 回復(fù)消息的分類及實(shí)體的創(chuàng)建
這篇文章主要為大家詳細(xì)介紹了Java微信公眾平臺開發(fā)第四步,回復(fù)消息的分類及實(shí)體的創(chuàng)建,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-04-04java學(xué)習(xí)筆記_關(guān)于字符串概述
下面小編就為大家?guī)硪黄猨ava學(xué)習(xí)筆記_關(guān)于字符串概述。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-05-05java實(shí)現(xiàn)微信點(diǎn)餐申請微信退款
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)微信點(diǎn)餐申請微信退款,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-09-09SpringCloud使用AOP統(tǒng)一處理Web請求日志實(shí)現(xiàn)步驟
這篇文章主要為大家介紹了SpringCloud使用AOP統(tǒng)一處理Web請求日志實(shí)現(xiàn)步驟,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08Java實(shí)用技巧:如何使用String去除開頭的第一個字符?
這篇文章主要介紹了Java實(shí)用技巧:如何使用String去除開頭的第一個字符,需要的朋友可以參考下2023-11-11Java并發(fā)之原子性 有序性 可見性及Happen Before原則
一提到happens-before原則,就讓人有點(diǎn)“丈二和尚摸不著頭腦”。這個涵蓋了整個JMM中可見性原則的規(guī)則,究竟如何理解,把我個人一些理解記錄下來。下面可以和小編一起學(xué)習(xí)Java 并發(fā)四個原則2021-09-09