java中關于深拷貝的幾種方式總結
前言
在java里,當我們需要拷貝一個對象時,有兩種類型的拷貝:淺拷貝與深拷貝。
- 淺拷貝只是拷貝了源對象的地址,所以源對象的值發(fā)生變化時,拷貝對象的值也會發(fā)生變化。
- 深拷貝則是拷貝了源對象的所有值,所以即使源對象的值發(fā)生變化時,拷貝對象的值也不會改變。

方式1:構造函數(shù)深拷貝
我們可以調用構造函數(shù)進行深拷貝,形參如果是基本類型和字符串則是直接賦值,如果是對象,則是重新new一個。
測試案例
package com.lyj.demo.pojo.cloneTest;
import lombok.Getter;
/**
* @author 凌兮
* @date 2021/4/15 14:28
* 通過構造器進行深拷貝測試
*/
@Getter
public class UserConstruct {
private String userName;
private AddressConstruct address;
public UserConstruct() {
}
public UserConstruct(String userName, AddressConstruct address) {
this.userName = userName;
this.address = address;
}
public static void main(String[] args) {
AddressConstruct address = new AddressConstruct("小區(qū)1", "小區(qū)2");
UserConstruct user = new UserConstruct("小李", address);
// 調用構造函數(shù)進行深拷貝
UserConstruct copyUser = new UserConstruct(user.getUserName(), new AddressConstruct(address.getAddress1(), address.getAddress2()));
// 修改源對象的值
user.getAddress().setAddress1("小區(qū)3");
// false
System.out.println(user == copyUser);
// false
System.out.println(user.getAddress().getAddress1() == copyUser.getAddress().getAddress1());
// false
System.out.println(user.getAddress().getAddress1().equals(copyUser.getAddress().getAddress1()));
// true
System.out.println(user.getAddress().getAddress2().equals(copyUser.getAddress().getAddress2()));
}
}
package com.lyj.demo.pojo.cloneTest;
import lombok.Getter;
import lombok.Setter;
/**
* @author 凌兮
* @date 2021/4/15 14:28
*/
@Getter
@Setter
public class AddressConstruct {
private String address1;
private String address2;
public AddressConstruct() {
}
public AddressConstruct(String address1, String address2) {
this.address1 = address1;
this.address2 = address2;
}
}
方式2:重載Clone()方法深拷貝
Object父類有個clone()的拷貝方法,不過它是protected類型的 ,我們需要重寫它并修改為public類型,除此之外,子類還需要實現(xiàn)Cloneable接口來告訴JVM這個類上市可以拷貝的。
測試案例
package com.lyj.demo.pojo.cloneTest;
import lombok.Getter;
import lombok.Setter;
/**
* @author 凌兮
* @date 2021/4/15 14:49
*
*/
@Setter
@Getter
public class AddressClone implements Cloneable{
private String address1;
private String address2;
public AddressClone() {
}
public AddressClone(String address1, String address2) {
this.address1 = address1;
this.address2 = address2;
}
@Override
protected AddressClone clone() throws CloneNotSupportedException {
return (AddressClone) super.clone();
}
}
package com.lyj.demo.pojo.cloneTest;
import lombok.Getter;
import lombok.Setter;
/**
* @author 凌兮
* @date 2021/4/15 14:48
* 通過實現(xiàn)Clone接口實現(xiàn)深拷貝
*/
@Setter
@Getter
public class UserClone implements Cloneable{
private String userName;
private AddressClone address;
public UserClone() {
}
public UserClone(String userName, AddressClone address) {
this.userName = userName;
this.address = address;
}
/**
* Object父類有個clone()的拷貝方法,不過它是protected類型的,
* 我們需要重寫它并修改為public類型。除此之外,
* 子類還需要實現(xiàn)Cloneable接口來告訴JVM這個類是可以拷貝的。
* @return
* @throws CloneNotSupportedException
*/
@Override
protected UserClone clone() throws CloneNotSupportedException {
// 需要注意的是,super.clone()其實是淺拷貝,
// 所以在重寫UserClone類的clone()方法時,address對象需要調用address.clone()重新賦值
UserClone userClone = (UserClone) super.clone();
userClone.setAddress(this.address.clone());
return userClone;
}
public static void main(String[] args) throws CloneNotSupportedException {
AddressClone address = new AddressClone("小區(qū)1", "小區(qū)2");
UserClone user = new UserClone("小李", address);
UserClone copyUser = user.clone();
user.getAddress().setAddress1("小區(qū)3");
// false
System.out.println(user == copyUser);
// false
System.out.println(user.getAddress().getAddress1().equals(copyUser.getAddress().getAddress1()));
}
}
需要注意的是,super.clone()其實是淺拷貝,所以在重寫User類的clone()方法時,address對象需要調用address.clone()重新賦值。
方式3:Apache Commons Lang序列化方式深拷貝
Java提供了序列化的能力,我們可以先將源對象進行序列化,再反序列化生成拷貝對象。但是,使用序列化的前提是拷貝的類(包括其成員變量)需要實現(xiàn)Serializable接口。
Apache Commons Lang包對Java序列化進行了封裝,我們可以直接使用它。
測試案例
package com.lyj.demo.pojo.cloneTest;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
/**
* @author 凌兮
* @date 2021/4/15 15:11
*/
@Getter
@Setter
public class AddressSerializable implements Serializable {
private String address1;
private String address2;
public AddressSerializable() {
}
public AddressSerializable(String address1, String address2) {
this.address1 = address1;
this.address2 = address2;
}
}
package com.lyj.demo.pojo.cloneTest;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang3.SerializationUtils;
import java.io.Serializable;
/**
* @author 凌兮
* @date 2021/4/15 15:10
* 通過Apache Commons Lang 序列化方式深拷貝
* Java提供了序列化的能力,我們可以先將源對象進行序列化,再反序列化生成拷貝對象。
* 但是,使用序列化的前提是拷貝的類(包括其成員變量)需要實現(xiàn)Serializable接口。
* Apache Commons Lang包對Java序列化進行了封裝,我們可以直接使用它。
*/
@Getter
@Setter
public class UserSerializable implements Serializable {
private String userName;
private AddressSerializable address;
public UserSerializable() {
}
public UserSerializable(String userName, AddressSerializable address) {
this.userName = userName;
this.address = address;
}
public static void main(String[] args) {
AddressSerializable address = new AddressSerializable("小區(qū)1", "小區(qū)2");
UserSerializable user = new UserSerializable("小李", address);
UserSerializable copyUser = SerializationUtils.clone(user);
user.getAddress().setAddress1("小區(qū)3");
// false
System.out.println(user == copyUser);
// false
System.out.println(user.getAddress().getAddress1().equals(copyUser.getAddress().getAddress1()));
}
}
方式4:Gson序列化方式深拷貝
Gson可以將對象序列化成JSON,也可以將JSON反序列化成對象,所以我們可以用它進行深拷貝。
測試案例
package com.lyj.demo.pojo.cloneTest;
import lombok.Data;
/**
* @author 凌兮
* @date 2021/4/15 15:31
*/
@Data
public class AddressGson {
private String address1;
private String address2;
public AddressGson() {
}
public AddressGson(String address1, String address2) {
this.address1 = address1;
this.address2 = address2;
}
}
package com.lyj.demo.pojo.cloneTest;
import com.google.gson.Gson;
import lombok.Data;
/**
* @author 凌兮
* @date 2021/4/15 15:30
* 使用Gson序列化方式進行深拷貝
* Gson可以將對象序列化成JSON,也可以將JSON反序列化成對象,所以我們可以用它進行深拷貝
*/
@Data
public class UserGson {
private String userName;
private AddressGson address;
public UserGson() {
}
public UserGson(String userName, AddressGson address) {
this.userName = userName;
this.address = address;
}
public static void main(String[] args) {
AddressGson address = new AddressGson("小區(qū)1", "小區(qū)2");
UserGson user = new UserGson("小李", address);
// 使用Gson序列化進行深拷貝
Gson gson = new Gson();
UserGson copyUser = gson.fromJson(gson.toJson(user), UserGson.class);
user.getAddress().setAddress1("小區(qū)3");
// false
System.out.println(user == copyUser);
// false
System.out.println(user.getAddress().getAddress1().equals(copyUser.getAddress().getAddress1()));
}
}
方式5:Jackson序列化方式
Jackson與Gson相似,可以將對象序列化成JSON,明顯不同的地方是拷貝的類(包括其成員變量)需要有默認的無參構造函數(shù)。
測試案例
package com.lyj.demo.pojo.cloneTest;
import lombok.Data;
/**
* @author 凌兮
* @date 2021/4/15 15:41
*/
@Data
public class AddressJackson {
private String address1;
private String address2;
public AddressJackson() {
}
public AddressJackson(String address1, String address2) {
this.address1 = address1;
this.address2 = address2;
}
}
package com.lyj.demo.pojo.cloneTest;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Data;
/**
* @author 凌兮
* @date 2021/4/15 15:40
* 通過Jackson方式實現(xiàn)深拷貝
* Jackson與Gson相似,可以將對象序列化成JSON,明顯不同的地方是拷貝的類(包括其成員變量)需要有默認的無參構造函數(shù)。
*/
@Data
public class UserJackson {
private String userName;
private AddressJackson address;
public UserJackson() {
}
public UserJackson(String userName, AddressJackson address) {
this.userName = userName;
this.address = address;
}
public static void main(String[] args) throws JsonProcessingException {
AddressJackson address = new AddressJackson("小區(qū)1", "小區(qū)2");
UserJackson user = new UserJackson("小李", address);
// 使用Jackson序列化進行深拷貝
ObjectMapper objectMapper = new ObjectMapper();
UserJackson copyUser = objectMapper.readValue(objectMapper.writeValueAsString(user), UserJackson.class);
user.getAddress().setAddress1("小區(qū)3");
// false
System.out.println(user == copyUser);
// false
System.out.println(user.getAddress().getAddress1().equals(copyUser.getAddress().getAddress1()));
}
}
總結
| 深拷貝方法 | 優(yōu)點 | 缺點 |
|---|---|---|
| 構造函數(shù) | 1. 底層實現(xiàn)簡單 2. 不需要引入第三方包 3. 系統(tǒng)開銷小 4. 對拷貝類沒有要求,不需要實現(xiàn)額外接口和方法 | 1. 可用性差,每次新增成員變量都需要新增新的拷貝構造函數(shù) |
| 重載clone()方法 | 1. 底層實現(xiàn)較簡單 2. 不需要引入第三方包 3. 系統(tǒng)開銷小 | 1. 可用性較差,每次新增成員變量可能需要修改clone()方法 2. 拷貝類(包括其成員變量)需要實現(xiàn)Cloneable接口 |
| Apache Commons Lang序列化 | 1. 可用性強,新增成員變量不需要修改拷貝方法 | 1. 底層實現(xiàn)較復雜 2. 需要引入Apache Commons Lang第三方JAR包 3. 拷貝類(包括其成員變量)需要實現(xiàn)Serializable接口 4. 序列化與反序列化存在一定的系統(tǒng)開銷 |
| Gson序列化 | 1. 可用性強,新增成員變量不需要修改拷貝方法 2. 對拷貝類沒有要求,不需要實現(xiàn)額外接口和方法 | 1. 底層實現(xiàn)復雜 2. 需要引入Gson第三方JAR包 3. 序列化與反序列化存在一定的系統(tǒng)開銷 |
| Jackson序列化 | 1. 可用性強,新增成員變量不需要修改拷貝方法 | 1. 底層實現(xiàn)復雜 2. 需要引入Jackson第三方JAR包 3. 拷貝類(包括其成員變量)需要實現(xiàn)默認的無參構造函數(shù) 4. 序列化與反序列化存在一定的系統(tǒng)開銷 |
以上為個人經驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
Java基于Runtime調用外部程序出現(xiàn)阻塞的解決方法
這篇文章主要介紹了Java基于Runtime調用外部程序出現(xiàn)阻塞的解決方法,是一個非常實用的技巧,需要的朋友可以參考下2014-09-09
Spring Boot自定義配置屬性源(PropertySource)
這篇文章主要介紹了Spring Boot自定義配置屬性源(PropertySource),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-06-06
Spring Cloud Ribbon實現(xiàn)客戶端負載均衡的方法
本篇文章主要介紹了Spring Cloud Ribbon實現(xiàn)客戶端負載均衡的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-05-05

