Java對象進行深拷貝的五種方法實例代碼
前言
在 Java 語言里,當我們需要拷貝一個對象時,有兩種類型的拷貝:淺拷貝與深拷貝。淺拷貝只是拷貝了源對象的地址,所以源對象的值發(fā)生變化時,拷貝對象的值也會發(fā)生變化。而深拷貝則是拷貝了源對象的所有值,所以即使源對象的值發(fā)生變化時,拷貝對象的值也不會改變。如下圖描述:

了解了淺拷貝和深拷貝的區(qū)別之后,本篇博客將教大家?guī)追N深拷貝的方法。
拷貝對象
首先,我們定義一下需要拷貝的簡單對象。
/**
* 用戶
*/
public class User {
private String name;
private Address address;
// constructors, getters and setters }
/**
* 地址
*/
public class Address {
private String city;
private String country;
// constructors, getters and setters
}
如上述代碼,我們定義了一個User用戶類,包含name姓名,和address地址,其中address并不是字符串,而是另一個Address類,包含country國家和city城市。構(gòu)造方法和成員變量的get()、set()方法此處我們省略不寫。接下來我們將詳細描述如何深拷貝User對象。
方法一 構(gòu)造函數(shù)
我們可以通過在調(diào)用構(gòu)造函數(shù)進行深拷貝,形參如果是基本類型和字符串則直接賦值,如果是對象則重新new一個。
測試用例
@Test
public void constructorCopy() {
Address address = new Address("安徽", "中國");
User user = new User("TM", address);
// 調(diào)用構(gòu)造函數(shù)時進行深拷貝
User copyUser = new User(user.getName(), new Address(address.getCity(), address.getCountry()));
// 修改源對象的值
user.getAddress().setCity("合肥");
// 檢查兩個對象的值不同
assertNotSame(user.getAddress().getCity(), copyUser.getAddress().getCity());
}
方法二 重載clone()方法
Object父類有個clone()的拷貝方法,不過它是protected類型的,我們需要重寫它并修改為public類型。除此之外,子類還需要實現(xiàn)Cloneable接口來告訴JVM這個類是可以拷貝的。
重寫代碼
讓我們修改一下User類,Address類,實現(xiàn)Cloneable接口,使其支持深拷貝。
/**
* 地址
*/
public class Address implements Cloneable {
private String city;
private String country;
// constructors, getters and setters
@Override
public Address clone() throws CloneNotSupportedException {
return (Address) super.clone();
}
}
/**
* 用戶
*/
public class User implements Cloneable {
private String name;
private Address address;
// constructors, getters and setters
@Override
public User clone() throws CloneNotSupportedException {
User user = (User) super.clone();
user.setAddress(this.address.clone());
return user;
}
}
需要注意的是,super.clone()其實是淺拷貝,所以在重寫User類的clone()方法時,address對象需要調(diào)用address.clone()重新賦值。
測試用例
@Test
public void cloneCopy() throws CloneNotSupportedException {
Address address = new Address("安徽", "中國");
User user = new User("TM", address);
// 調(diào)用clone()方法進行深拷貝
User copyUser = user.clone();
// 修改源對象的值
user.getAddress().setCity("合肥");
// 檢查兩個對象的值不同
assertNotSame(user.getAddress().getCity(), copyUser.getAddress().getCity());
}
方法三 Apache Commons Lang序列化
Java提供了序列化的能力,我們可以先將源對象進行序列化,再反序列化生成拷貝對象。但是,使用序列化的前提是拷貝的類(包括其成員變量)需要實現(xiàn)Serializable接口。Apache Commons Lang包對Java序列化進行了封裝,我們可以直接使用它。
重寫代碼
讓我們修改一下User類,Address類,實現(xiàn)Serializable接口,使其支持序列化。
/**
* 地址
*/
public class Address implements Serializable {
private String city;
private String country;
// constructors, getters and setters
}
/**
* 用戶
*/
public class User implements Serializable {
private String name;
private Address address;
// constructors, getters and setters }
測試用例
@Test
public void serializableCopy() {
Address address = new Address("安徽", "中國");
User user = new User("TM", address);
// 使用Apache Commons Lang序列化進行深拷貝
User copyUser = (User) SerializationUtils.clone(user);
// 修改源對象的值
user.getAddress().setCity("合肥");
// 檢查兩個對象的值不同
assertNotSame(user.getAddress().getCity(), copyUser.getAddress().getCity());
}
方法四 Gson序列化
Gson可以將對象序列化成JSON,也可以將JSON反序列化成對象,所以我們可以用它進行深拷貝。
測試用例
@Test
public void gsonCopy() {
Address address = new Address("安徽", "中國");
User user = new User("TM", address);
// 使用Gson序列化進行深拷貝
Gson gson = new Gson();
User copyUser = gson.fromJson(gson.toJson(user), User.class);
// 修改源對象的值
user.getAddress().setCity("合肥");
// 檢查兩個對象的值不同
assertNotSame(user.getAddress().getCity(), copyUser.getAddress().getCity());
}
方法五 Jackson序列化
Jackson與Gson相似,可以將對象序列化成JSON,明顯不同的地方是拷貝的類(包括其成員變量)需要有默認的無參構(gòu)造函數(shù)。
重寫代碼
讓我們修改一下User類,Address類,實現(xiàn)默認的無參構(gòu)造函數(shù),使其支持Jackson。
/**
* 用戶
*/
public class User {
private String name;
private Address address;
// constructors, getters and setters
public User() {
}
}
/**
* 地址
*/
public class Address {
private String city;
private String country;
// constructors, getters and setters
public Address() {
}
}
測試用例
@Test
public void jacksonCopy() throws IOException {
Address address = new Address("安徽", "中國");
User user = new User("TM", address);
// 使用Jackson序列化進行深拷貝
ObjectMapper objectMapper = new ObjectMapper();
User copyUser = objectMapper.readValue(objectMapper.writeValueAsString(user), User.class);
// 修改源對象的值
user.getAddress().setCity("合肥");
// 檢查兩個對象的值不同
assertNotSame(user.getAddress().getCity(), copyUser.getAddress().getCity());
}
總結(jié)
說了這么多深拷貝的實現(xiàn)方法,哪一種方法才是最好的呢?最簡單的判斷就是根據(jù)拷貝的類(包括其成員變量)是否提供了深拷貝的構(gòu)造函數(shù)、是否實現(xiàn)了Cloneable接口、是否實現(xiàn)了Serializable接口、是否實現(xiàn)了默認的無參構(gòu)造函數(shù)來進行選擇。如果需要詳細的考慮,則可以參考下面的表格:
| 深拷貝方法 | 優(yōu)點 | 缺點 |
|---|---|---|
| 構(gòu)造函數(shù) | 1.底層實現(xiàn)簡單 2.不需要引入第三方包 3.系統(tǒng)開銷小 4.對拷貝類沒有要求,不需要實現(xiàn)額外接口和方法 | 1.可用性差,每次新增成員變量都需要新增新的拷貝構(gòu)造函數(shù) |
| 重載clone()方法 | 1.底層實現(xiàn)較簡單 2.不需要引入第三方包 3.系統(tǒng)開銷小 | 1.可用性較差,每次新增成員變量可能需要修改clone()方法 2.拷貝類(包括其成員變量)需要實現(xiàn)Cloneable接囗 |
| Apache CommonsLang序列化 | 1.可用性強,新增成員變量不需要修改拷貝方法 | 1.底層實現(xiàn)較復(fù)雜 2.需要引入Apache Commons Lang第二方JAR包 3.拷貝類(包括其成員變量)需要實現(xiàn)Serializable接口 4.序列化與反序列化存在一定的系統(tǒng)開銷 |
| Gson序列化 | 1.可用性強,新增成員變量不需要修改拷貝方法 2.對拷貝類沒有要求,不需要實現(xiàn)額外接口和方法 | 1.底層實現(xiàn)復(fù)雜 2.需要引入Gson第三方JAR包 3.序列化與反序列化存在一定的系統(tǒng)開銷 |
| Jackson序列化 | 1.可用性強,新增成員變量不需要修改拷貝方法 | 1.底層實現(xiàn)復(fù)雜 2.需要引入Jackson第三方JAR包 3.拷貝類(包括其成員變量)需要實現(xiàn)默認的無參構(gòu)造函數(shù) 4.序列化與反序列化存在一定的系統(tǒng)開 |
到此這篇關(guān)于Java對象進行深拷貝的五種方法的文章就介紹到這了,更多相關(guān)Java對象深拷貝內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
往DAO類中注入@PersistenceContext和@Resource的區(qū)別詳解
這篇文章主要介紹了往DAO類中注入@PersistenceContext和@Resource的區(qū)別詳解,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-02-02
SpringBoot2開發(fā)從0開始Spring?Initailizr初始化
這篇文章主要為大家介紹了SpringBoot2從0開始lombok、devtools、Spring?Initailizr的開發(fā)技巧,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-05-05
prometheus數(shù)據(jù)遠程寫入elasticsearch方式
這篇文章主要介紹了prometheus數(shù)據(jù)遠程寫入elasticsearch方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2025-06-06
使用Java解決數(shù)組旋轉(zhuǎn)問題的方法詳解
給定一個包含n個整數(shù)的數(shù)組,要求將數(shù)組中的元素向前移動m個位置,即數(shù)組的前n-m個元素順序向后移動m個位置,最后m個元素移動到數(shù)組的最前面,本文給大家介紹了如何使用Java解決數(shù)組旋轉(zhuǎn)問題,需要的朋友可以參考下2026-03-03

