Java的深拷貝與淺拷貝的幾種實(shí)現(xiàn)方式
1、介紹
關(guān)于Java的深拷貝和淺拷貝,簡(jiǎn)單來說就是創(chuàng)建一個(gè)和已知對(duì)象一模一樣的對(duì)象。可能日常編碼過程中用的不多,但是這是一個(gè)面試經(jīng)常會(huì)問的問題,而且了解深拷貝和淺拷貝的原理,對(duì)于Java中的所謂值傳遞或者引用傳遞將會(huì)有更深的理解。
2、淺拷貝
淺拷貝就是獲得拷貝對(duì)象的引用,而不是正真意義上的拷貝一個(gè)對(duì)象,例如
A a = new A(); A b = a;
此時(shí)引用變量a和b 同時(shí)指向了同一個(gè)堆中的內(nèi)存空間,變量b只是復(fù)制了實(shí)例A的引用地址,并不是重新在堆中開辟了一個(gè)新的空間位置,來完整的復(fù)制實(shí)例A 如圖
3、深拷貝
深拷貝則是拷貝了源對(duì)象的所有值,所以即使源對(duì)象的值發(fā)生變化時(shí),拷貝對(duì)象的值也不會(huì)改變。深拷貝則是真正意義上的拷貝
,如圖
4、深拷貝和淺拷貝的區(qū)別
簡(jiǎn)單來說就是一句話: 深拷貝和淺拷貝最根本的區(qū)別在于是否真正獲取一個(gè)對(duì)象的復(fù)制實(shí)體,而不是引用。
5、淺拷貝的實(shí)現(xiàn)
首先,我們定義一下需要拷貝的簡(jiǎn)單對(duì)象。
public class Student{ private String name; private int age; private String sex; } public class School { private String schoolName; private int stuNums; private Student stu; }
如上述代碼,我們定義了一個(gè)Student學(xué)生類,包含name姓名,和age年齡,sex性別,而是另一個(gè)School類,包含schoolName學(xué)校名稱和stuNums學(xué)生數(shù)量以及Student學(xué)生,其中Student并不是字符串,而是一個(gè)Student類。接下來我們將詳細(xì)描述如何簽拷貝School對(duì)象。
我們看如下這段代碼:
public class Student{ private String name; private int age; private String sex; 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 String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } @Override public String toString() { return "Student [name=" + name + ", age=" + age + ", sex=" + sex + "]"; } }
public class School implements Cloneable{ private String schoolName; private int stuNums; private Student stu; public String getSchoolName() { return schoolName; } public void setSchoolName(String schoolName) { this.schoolName = schoolName; } public int getStuNums() { return stuNums; } public void setStuNums(int stuNums) { this.stuNums = stuNums; } public Student getStu() { return stu; } public void setStu(Student stu) { this.stu = stu; } @Override protected School clone() throws CloneNotSupportedException { // TODO Auto-generated method stub return (School)super.clone(); } @Override public String toString() { return "School [schoolName=" + schoolName + ", stuNums=" + stuNums + ", stu=" + stu + "]"; } }
這是一個(gè)我們要進(jìn)行賦值的原始類 School。下面我們產(chǎn)生一個(gè) School對(duì)象,并調(diào)用其 clone 方法復(fù)制一個(gè)新的對(duì)象。
注意:調(diào)用對(duì)象的 clone 方法,必須要讓類實(shí)現(xiàn) Cloneable 接口,并且覆寫 clone 方法。
public class TestClone { public static void main(String[] args) throws CloneNotSupportedException { //創(chuàng)建初始的School對(duì)象 School s1 = new School(); s1.setSchoolName("xx大學(xué)"); s1.setStuNums(2000); Student stu1 = new Student(); stu1.setAge(20); stu1.setName("肉丁"); stu1.setSex("女"); s1.setStu(stu1); School s2 = s1.clone(); //調(diào)用重寫的clone方法,clone出一個(gè)新的school---s2 System.out.println("s1: "+s1+" s1的hashcode:"+s1.hashCode()+" s1中stu1的hashcode:"+s1.getStu().hashCode()); System.out.println("s2: "+s2+" s2的hashcode:"+s2.hashCode()+" s2中stu1的hashcode:"+s2.getStu().hashCode());//System.out.println(s1.getStu().getAge()==s2.getStu().getAge()); System.out.println("----------------------------"); System.out.println("修改克隆出來的對(duì)象"); Student student2 = s2.getStu(); student2.setAge(21); student2.setName("斌"); s2.setStu(student2); System.out.println("s1: "+s1+" s1的hashcode:"+s1.hashCode()+" s1中stu1的hashcode:"+s1.getStu().hashCode()); System.out.println("s2: "+s2+" s2的hashcode:"+s2.hashCode()+" s2中stu1的hashcode:"+s2.getStu().hashCode());//System.out.println(s1.getStu().getAge()==s2.getStu().getAge()); } }
我們查看輸出的結(jié)果
s1: School [schoolName=xx大學(xué), stuNums=2000, stu=Student [name=肉丁, age=20, sex=女]]
s1的hashcode:500977346
s1中stu1的hashcode:20132171
s2: School [schoolName=xx大學(xué), stuNums=2000, stu=Student [name=肉丁, age=20, sex=女]]
s2的hashcode:186370029
s2中stu1的hashcode:20132171
修改克隆出來的對(duì)象
s1: School [schoolName=xx大學(xué), stuNums=2000, stu=Student [name=斌, age=21, sex=女]]
s1的hashcode:500977346
s1中stu1的hashcode:20132171
s2: School [schoolName=xx大學(xué), stuNums=2000, stu=Student [name=斌, age=21, sex=女]]
s2的hashcode:186370029
s2中stu1的hashcode:20132171
首先看原始類 School 實(shí)現(xiàn) Cloneable 接口,并且覆寫 clone 方法,它還有三個(gè)屬性,一個(gè)引用類型 String定義的 schoolName,一個(gè)基本類型 int定義的 stuNums,還有一個(gè)引用類型 Student,這是一個(gè)自定義類,這個(gè)類也包含三個(gè)屬性 name、age和 sex。
接著看測(cè)試內(nèi)容,首先我們創(chuàng)建一個(gè)School類的對(duì)象s1 ,其schoolName為xx大學(xué),stuNums為2000,學(xué)生類Stundet三個(gè)屬性為 20、肉丁和女。接著我們調(diào)用 clone() 方法復(fù)制另一個(gè)對(duì)象 s2,接著打印這兩個(gè)對(duì)象的內(nèi)容。
從第 2 行和第 5 行打印結(jié)果:
s1的hashcode:500977346
s2的hashcode:186370029
可以看出這是兩個(gè)不同的對(duì)象。
從第 1 行和第 4 行打印的對(duì)象內(nèi)容看,原對(duì)象 s1 和克隆出來的對(duì)象 s2 內(nèi)容完全相同。
代碼中我們只是更改了克隆對(duì)象 s2 的屬性Student 為斌、21、女(原對(duì)象 s1 是肉丁、20、女) ,但是從第 8 行和第 11 行打印結(jié)果來看,原對(duì)象 s1 和克隆對(duì)象 s2 的 Student屬性都被修改了。
也就是說對(duì)象 School的屬性 Student,經(jīng)過 clone 之后,其實(shí)只是復(fù)制了其引用,他們指向的還是同一塊堆內(nèi)存空間,當(dāng)修改其中一個(gè)對(duì)象的屬性 Student,另一個(gè)也會(huì)跟著變化。
6、深拷貝的實(shí)現(xiàn)
深拷貝的方式有很多種,文中我們將介紹三種方式
- 方法一 構(gòu)造函數(shù)
- 方法二 重載clone()方法
- 方法三Serializable序列化
6.1、構(gòu)造函數(shù)
public void constructorCopy() { Student student = new Student ("小李",21,"男"); School school = new School ("xx大學(xué)",100, student); // 調(diào)用構(gòu)造函數(shù)時(shí)進(jìn)行深拷貝 School copySchool = new School (school.getSchoolName(),school.getStuNums(), new Student(student.getName(), student.getAge(),student.getSex())); // 修改源對(duì)象的值 copySchool .getStudent().setSex("女"); // 檢查兩個(gè)對(duì)象的值不同 System.out.println(school.hashCode()==school2.hasCode()) }
6.2、重載clone()方法
Object父類有個(gè)clone()的拷貝方法,不過它是protected類型的,我們需要重寫它并修改為public類型。除此之外,子類還需要實(shí)現(xiàn)Cloneable接口來告訴JVM這個(gè)類是可以拷貝的。讓我們還是看之前的School代碼
public class School implements Cloneable{ private String schoolName; private int stuNums; private Student stu; public String getSchoolName() { return schoolName; } public void setSchoolName(String schoolName) { this.schoolName = schoolName; } public int getStuNums() { return stuNums; } public void setStuNums(int stuNums) { this.stuNums = stuNums; } public Student getStu() { return stu; } public void setStu(Student stu) { this.stu = stu; } @Override protected School clone() throws CloneNotSupportedException { School school = (School) super.clone(); school.stu = (Student) stu.clone(); return school; } @Override public String toString() { return "School [schoolName=" + schoolName + ", stuNums=" + stuNums + ", stu=" + stu + "]"; } }
public class Student implements Cloneable{ private String name; private int age; private String sex; 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 String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } @Override public String toString() { return "Student [name=" + name + ", age=" + age + ", sex=" + sex + "]"; } @Override protected Student clone() throws CloneNotSupportedException { return (Student)super.clone(); } }
我們查看輸出的結(jié)果
s1: School [schoolName=xx大學(xué), stuNums=2000, stu=Student [name=肉丁, age=20, sex=女]]
s1的hashcode:500977346
s1中stu1的hashcode:20132171
s2: School [schoolName=xx大學(xué), stuNums=2000, stu=Student [name=肉丁, age=20, sex=女]]
s2的hashcode:186370029
s2中stu1的hashcode:2094548358
修改克隆出來的對(duì)象
s1: School [schoolName=xx大學(xué), stuNums=2000, stu=Student [name=肉丁, age=20, sex=女]]
s1的hashcode:500977346
s1中stu1的hashcode:20132171
s2: School [schoolName=xx大學(xué), stuNums=2000, stu=Student [name=斌, age=21, sex=女]]
s2的hashcode:186370029
s2中stu1的hashcode:2094548358
需要注意的是,super.clone()其實(shí)是淺拷貝,所以在重寫School類的clone()方法時(shí),Student對(duì)象需要調(diào)用stu.clone()重新賦值。
查看第 2 行和第 5 行
s1的hashcode:500977346
s2的hashcode:186370029
查看第 3 行和第 6 行
s1中stu1的hashcode:20132171
s2中stu1的hashcode:2094548358
通過結(jié)果發(fā)現(xiàn)重新復(fù)制的對(duì)象s2和s1的hashCode不同,并且s1.stu與s2.stu2的hashCode也不同,由此證明復(fù)制的新的對(duì)象和原本的對(duì)象指向的不是同一個(gè)一個(gè)對(duì)象,意味著堆內(nèi)存中存在兩個(gè)School實(shí)例
6.3、Serializable序列化
我們看如下的代碼
import java.io.Serializable; public class User implements Serializable { private String name; private Address2 address; public User(String name, Address2 address) { this.name = name; this.address = address; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Address2 getAddress() { return address; } public void setAddress(Address2 address) { this.address = address; } public Object deepClone() throws Exception { // 序列化 ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(this); // 反序列化 ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); return ois.readObject(); } }
import java.io.Serializable; public class Address2 implements Serializable { private String city; private String country; public Address2(String city, String country) { this.city = city; this.country = country; } public String getCity() { return city; } public void setCity(String city) { this.city = city; } public String getCountry() { return country; } public void setCountry(String country) { this.country = country; } @Override public String toString() { return "Address2{" + "city='" + city + '\'' + ", country='" + country + '\'' + '}'; } }
注意 要使用序列化的方式來復(fù)制對(duì)象 對(duì)象需要繼承Serializable接口,接下來我們查看測(cè)試類
public static void main(String[] args) throws Exception { Address2 address = new Address2("大同", "中國"); User user = new User("yznl", address); User user2 = (User) user.deepClone(); System.out.println(user.toString()); System.out.println(user2.toString()); }
結(jié)果如下:
277630005,1915503092
通過比較user對(duì)象和克隆的user2對(duì)象的hashCode發(fā)現(xiàn),也是不同的對(duì)象
到此這篇關(guān)于Java的深拷貝與淺拷貝的幾種實(shí)現(xiàn)方式的文章就介紹到這了,更多相關(guān)Java 深拷貝與淺拷貝內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java?C++題解leetcode769最多能完成排序的塊
這篇文章主要為大家介紹了Java?C++題解leetcode769最多能完成排序的塊示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10詳解基于Spring Boot/Spring Session/Redis的分布式Session共享解決方案
本篇文章主要介紹了詳解基于Spring Boot/Spring Session/Redis的分布式Session共享解決方案 ,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06Java配置HTTP/Socks代理的簡(jiǎn)單快速上手方法
這篇文章主要為大家介紹了Java配置HTTP/Socks代理的簡(jiǎn)單快速上手方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08Java連接FTP服務(wù)器并使用ftp連接池進(jìn)行文件操作指南
使用FTP最主要的功能是對(duì)文件進(jìn)行管理,下面這篇文章主要給大家介紹了關(guān)于Java連接FTP服務(wù)器并使用ftp連接池進(jìn)行文件操作的相關(guān)資料,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-08-08Java后臺(tái)接口開發(fā)初步實(shí)戰(zhàn)教程
下面小編就為大家分享一篇 Java后臺(tái)接口開發(fā)初步實(shí)戰(zhàn)教程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-01-01Java將字符串轉(zhuǎn)化為數(shù)組的兩種方法
Java中的String類是一種特殊的字符串,它可以被用于處理字符串,Java中的String類也可以將字符串轉(zhuǎn)換為數(shù)組,下面這篇文章主要給大家介紹了關(guān)于Java將字符串轉(zhuǎn)化為數(shù)組的兩種方法,需要的朋友可以參考下2023-05-05