Java的深拷貝與淺拷貝的幾種實現(xiàn)方式
1、介紹
關(guān)于Java的深拷貝和淺拷貝,簡單來說就是創(chuàng)建一個和已知對象一模一樣的對象??赡苋粘>幋a過程中用的不多,但是這是一個面試經(jīng)常會問的問題,而且了解深拷貝和淺拷貝的原理,對于Java中的所謂值傳遞或者引用傳遞將會有更深的理解。
2、淺拷貝
淺拷貝就是獲得拷貝對象的引用,而不是正真意義上的拷貝一個對象,例如
A a = new A(); A b = a;
此時引用變量a和b 同時指向了同一個堆中的內(nèi)存空間,變量b只是復(fù)制了實例A的引用地址,并不是重新在堆中開辟了一個新的空間位置,來完整的復(fù)制實例A 如圖

3、深拷貝
深拷貝則是拷貝了源對象的所有值,所以即使源對象的值發(fā)生變化時,拷貝對象的值也不會改變。深拷貝則是真正意義上的拷貝,如圖

4、深拷貝和淺拷貝的區(qū)別
簡單來說就是一句話: 深拷貝和淺拷貝最根本的區(qū)別在于是否真正獲取一個對象的復(fù)制實體,而不是引用。
5、淺拷貝的實現(xiàn)
首先,我們定義一下需要拷貝的簡單對象。
public class Student{
private String name;
private int age;
private String sex;
}
public class School {
private String schoolName;
private int stuNums;
private Student stu;
}
如上述代碼,我們定義了一個Student學(xué)生類,包含name姓名,和age年齡,sex性別,而是另一個School類,包含schoolName學(xué)校名稱和stuNums學(xué)生數(shù)量以及Student學(xué)生,其中Student并不是字符串,而是一個Student類。接下來我們將詳細描述如何簽拷貝School對象。
我們看如下這段代碼:
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 + "]";
}
}
這是一個我們要進行賦值的原始類 School。下面我們產(chǎn)生一個 School對象,并調(diào)用其 clone 方法復(fù)制一個新的對象。
注意:調(diào)用對象的 clone 方法,必須要讓類實現(xiàn) Cloneable 接口,并且覆寫 clone 方法。
public class TestClone {
public static void main(String[] args) throws CloneNotSupportedException {
//創(chuàng)建初始的School對象
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出一個新的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("修改克隆出來的對象");
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
修改克隆出來的對象
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 實現(xiàn) Cloneable 接口,并且覆寫 clone 方法,它還有三個屬性,一個引用類型 String定義的 schoolName,一個基本類型 int定義的 stuNums,還有一個引用類型 Student,這是一個自定義類,這個類也包含三個屬性 name、age和 sex。
接著看測試內(nèi)容,首先我們創(chuàng)建一個School類的對象s1 ,其schoolName為xx大學(xué),stuNums為2000,學(xué)生類Stundet三個屬性為 20、肉丁和女。接著我們調(diào)用 clone() 方法復(fù)制另一個對象 s2,接著打印這兩個對象的內(nèi)容。
從第 2 行和第 5 行打印結(jié)果:
s1的hashcode:500977346
s2的hashcode:186370029
可以看出這是兩個不同的對象。
從第 1 行和第 4 行打印的對象內(nèi)容看,原對象 s1 和克隆出來的對象 s2 內(nèi)容完全相同。
代碼中我們只是更改了克隆對象 s2 的屬性Student 為斌、21、女(原對象 s1 是肉丁、20、女) ,但是從第 8 行和第 11 行打印結(jié)果來看,原對象 s1 和克隆對象 s2 的 Student屬性都被修改了。
也就是說對象 School的屬性 Student,經(jīng)過 clone 之后,其實只是復(fù)制了其引用,他們指向的還是同一塊堆內(nèi)存空間,當(dāng)修改其中一個對象的屬性 Student,另一個也會跟著變化。
6、深拷貝的實現(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ù)時進行深拷貝
School copySchool = new School (school.getSchoolName(),school.getStuNums(), new Student(student.getName(), student.getAge(),student.getSex()));
// 修改源對象的值
copySchool .getStudent().setSex("女");
// 檢查兩個對象的值不同
System.out.println(school.hashCode()==school2.hasCode())
}
6.2、重載clone()方法
Object父類有個clone()的拷貝方法,不過它是protected類型的,我們需要重寫它并修改為public類型。除此之外,子類還需要實現(xiàn)Cloneable接口來告訴JVM這個類是可以拷貝的。讓我們還是看之前的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
修改克隆出來的對象
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()其實是淺拷貝,所以在重寫School類的clone()方法時,Student對象需要調(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ù)制的對象s2和s1的hashCode不同,并且s1.stu與s2.stu2的hashCode也不同,由此證明復(fù)制的新的對象和原本的對象指向的不是同一個一個對象,意味著堆內(nèi)存中存在兩個School實例
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ù)制對象 對象需要繼承Serializable接口,接下來我們查看測試類
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對象和克隆的user2對象的hashCode發(fā)現(xiàn),也是不同的對象
到此這篇關(guān)于Java的深拷貝與淺拷貝的幾種實現(xiàn)方式的文章就介紹到這了,更多相關(guān)Java 深拷貝與淺拷貝內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springboot中nacos-client獲取配置的實現(xiàn)方法
本文主要介紹了springboot中nacos-client獲取配置的實現(xiàn)方法,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-04-04
Java內(nèi)存分配與JVM參數(shù)詳解(推薦)
本文詳解JVM內(nèi)存結(jié)構(gòu)與參數(shù)調(diào)整,涵蓋堆分代、元空間、GC選擇及優(yōu)化策略,幫助開發(fā)者提升性能、避免內(nèi)存泄漏,本文給大家介紹Java內(nèi)存分配與JVM參數(shù)詳解,感興趣的朋友一起看看吧2025-06-06
Java Kafka分區(qū)發(fā)送及消費實戰(zhàn)
本文主要介紹了Kafka分區(qū)發(fā)送及消費實戰(zhàn),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07
Springboot?Filter中注入bean無效為null問題
這篇文章主要介紹了Springboot?Filter中注入bean無效為null問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-05-05
Spring聲明式事務(wù)@Transactional注解實現(xiàn)元數(shù)據(jù)驅(qū)動的事務(wù)管理
這篇文章主要為大家介紹了Spring聲明式事務(wù)@Transactional注解實現(xiàn)元數(shù)據(jù)驅(qū)動的事務(wù)管理示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-10-10
mybatis中實現(xiàn)枚舉自動轉(zhuǎn)換方法詳解
在使用mybatis的時候經(jīng)常會遇到枚舉類型的轉(zhuǎn)換,下面這篇文章主要給大家介紹了關(guān)于mybatis中實現(xiàn)枚舉自動轉(zhuǎn)換的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起看看吧。2017-08-08

