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

方式1:構(gòu)造函數(shù)深拷貝
我們可以調(diào)用構(gòu)造函數(shù)進(jìn)行深拷貝,形參如果是基本類型和字符串則是直接賦值,如果是對(duì)象,則是重新new一個(gè)。
測(cè)試案例
package com.lyj.demo.pojo.cloneTest;
import lombok.Getter;
/**
* @author 凌兮
* @date 2021/4/15 14:28
* 通過(guò)構(gòu)造器進(jìn)行深拷貝測(cè)試
*/
@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);
// 調(diào)用構(gòu)造函數(shù)進(jìn)行深拷貝
UserConstruct copyUser = new UserConstruct(user.getUserName(), new AddressConstruct(address.getAddress1(), address.getAddress2()));
// 修改源對(duì)象的值
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父類有個(gè)clone()的拷貝方法,不過(guò)它是protected類型的 ,我們需要重寫它并修改為public類型,除此之外,子類還需要實(shí)現(xiàn)Cloneable接口來(lái)告訴JVM這個(gè)類上市可以拷貝的。
測(cè)試案例
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
* 通過(guò)實(shí)現(xiàn)Clone接口實(shí)現(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父類有個(gè)clone()的拷貝方法,不過(guò)它是protected類型的,
* 我們需要重寫它并修改為public類型。除此之外,
* 子類還需要實(shí)現(xiàn)Cloneable接口來(lái)告訴JVM這個(gè)類是可以拷貝的。
* @return
* @throws CloneNotSupportedException
*/
@Override
protected UserClone clone() throws CloneNotSupportedException {
// 需要注意的是,super.clone()其實(shí)是淺拷貝,
// 所以在重寫UserClone類的clone()方法時(shí),address對(duì)象需要調(diào)用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()其實(shí)是淺拷貝,所以在重寫User類的clone()方法時(shí),address對(duì)象需要調(diào)用address.clone()重新賦值。
方式3:Apache Commons Lang序列化方式深拷貝
Java提供了序列化的能力,我們可以先將源對(duì)象進(jìn)行序列化,再反序列化生成拷貝對(duì)象。但是,使用序列化的前提是拷貝的類(包括其成員變量)需要實(shí)現(xiàn)Serializable接口。
Apache Commons Lang包對(duì)Java序列化進(jìn)行了封裝,我們可以直接使用它。
測(cè)試案例
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
* 通過(guò)Apache Commons Lang 序列化方式深拷貝
* Java提供了序列化的能力,我們可以先將源對(duì)象進(jìn)行序列化,再反序列化生成拷貝對(duì)象。
* 但是,使用序列化的前提是拷貝的類(包括其成員變量)需要實(shí)現(xiàn)Serializable接口。
* Apache Commons Lang包對(duì)Java序列化進(jìn)行了封裝,我們可以直接使用它。
*/
@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可以將對(duì)象序列化成JSON,也可以將JSON反序列化成對(duì)象,所以我們可以用它進(jìn)行深拷貝。
測(cè)試案例
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序列化方式進(jìn)行深拷貝
* Gson可以將對(duì)象序列化成JSON,也可以將JSON反序列化成對(duì)象,所以我們可以用它進(jìn)行深拷貝
*/
@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序列化進(jìn)行深拷貝
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相似,可以將對(duì)象序列化成JSON,明顯不同的地方是拷貝的類(包括其成員變量)需要有默認(rèn)的無(wú)參構(gòu)造函數(shù)。
測(cè)試案例
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
* 通過(guò)Jackson方式實(shí)現(xiàn)深拷貝
* Jackson與Gson相似,可以將對(duì)象序列化成JSON,明顯不同的地方是拷貝的類(包括其成員變量)需要有默認(rèn)的無(wú)參構(gòu)造函數(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序列化進(jìn)行深拷貝
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()));
}
}
總結(jié)
| 深拷貝方法 | 優(yōu)點(diǎn) | 缺點(diǎn) |
|---|---|---|
| 構(gòu)造函數(shù) | 1. 底層實(shí)現(xiàn)簡(jiǎn)單 2. 不需要引入第三方包 3. 系統(tǒng)開(kāi)銷小 4. 對(duì)拷貝類沒(méi)有要求,不需要實(shí)現(xiàn)額外接口和方法 | 1. 可用性差,每次新增成員變量都需要新增新的拷貝構(gòu)造函數(shù) |
| 重載clone()方法 | 1. 底層實(shí)現(xiàn)較簡(jiǎn)單 2. 不需要引入第三方包 3. 系統(tǒng)開(kāi)銷小 | 1. 可用性較差,每次新增成員變量可能需要修改clone()方法 2. 拷貝類(包括其成員變量)需要實(shí)現(xiàn)Cloneable接口 |
| Apache Commons Lang序列化 | 1. 可用性強(qiáng),新增成員變量不需要修改拷貝方法 | 1. 底層實(shí)現(xiàn)較復(fù)雜 2. 需要引入Apache Commons Lang第三方JAR包 3. 拷貝類(包括其成員變量)需要實(shí)現(xiàn)Serializable接口 4. 序列化與反序列化存在一定的系統(tǒng)開(kāi)銷 |
| Gson序列化 | 1. 可用性強(qiáng),新增成員變量不需要修改拷貝方法 2. 對(duì)拷貝類沒(méi)有要求,不需要實(shí)現(xiàn)額外接口和方法 | 1. 底層實(shí)現(xiàn)復(fù)雜 2. 需要引入Gson第三方JAR包 3. 序列化與反序列化存在一定的系統(tǒng)開(kāi)銷 |
| Jackson序列化 | 1. 可用性強(qiáng),新增成員變量不需要修改拷貝方法 | 1. 底層實(shí)現(xiàn)復(fù)雜 2. 需要引入Jackson第三方JAR包 3. 拷貝類(包括其成員變量)需要實(shí)現(xiàn)默認(rèn)的無(wú)參構(gòu)造函數(shù) 4. 序列化與反序列化存在一定的系統(tǒng)開(kāi)銷 |
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java基于Runtime調(diào)用外部程序出現(xiàn)阻塞的解決方法
這篇文章主要介紹了Java基于Runtime調(diào)用外部程序出現(xiàn)阻塞的解決方法,是一個(gè)非常實(shí)用的技巧,需要的朋友可以參考下2014-09-09
SpringBoot如何進(jìn)行業(yè)務(wù)校驗(yàn)實(shí)例詳解
這篇文章主要給大家介紹了關(guān)于SpringBoot如何進(jìn)行業(yè)務(wù)校驗(yàn)的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2022-01-01
Spring Boot自定義配置屬性源(PropertySource)
這篇文章主要介紹了Spring Boot自定義配置屬性源(PropertySource),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-06-06
Spring Cloud Ribbon實(shí)現(xiàn)客戶端負(fù)載均衡的方法
本篇文章主要介紹了Spring Cloud Ribbon實(shí)現(xiàn)客戶端負(fù)載均衡的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-05-05

