java中關(guān)于深拷貝的幾種方式總結(jié)
前言
在java里,當(dāng)我們需要拷貝一個對象時,有兩種類型的拷貝:淺拷貝與深拷貝。
- 淺拷貝只是拷貝了源對象的地址,所以源對象的值發(fā)生變化時,拷貝對象的值也會發(fā)生變化。
- 深拷貝則是拷貝了源對象的所有值,所以即使源對象的值發(fā)生變化時,拷貝對象的值也不會改變。
方式1:構(gòu)造函數(shù)深拷貝
我們可以調(diào)用構(gòu)造函數(shù)進行深拷貝,形參如果是基本類型和字符串則是直接賦值,如果是對象,則是重新new一個。
測試案例
package com.lyj.demo.pojo.cloneTest; import lombok.Getter; /** * @author 凌兮 * @date 2021/4/15 14:28 * 通過構(gòu)造器進行深拷貝測試 */ @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ù)進行深拷貝 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對象需要調(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()其實是淺拷貝,所以在重寫User類的clone()方法時,address對象需要調(diào)用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,明顯不同的地方是拷貝的類(包括其成員變量)需要有默認的無參構(gòu)造函數(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,明顯不同的地方是拷貝的類(包括其成員變量)需要有默認的無參構(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序列化進行深拷貝 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)點 | 缺點 |
---|---|---|
構(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 Commons Lang序列化 | 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)開銷 |
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java基于Runtime調(diào)用外部程序出現(xiàn)阻塞的解決方法
這篇文章主要介紹了Java基于Runtime調(diào)用外部程序出現(xiàn)阻塞的解決方法,是一個非常實用的技巧,需要的朋友可以參考下2014-09-09SpringBoot如何進行業(yè)務(wù)校驗實例詳解
這篇文章主要給大家介紹了關(guān)于SpringBoot如何進行業(yè)務(wù)校驗的相關(guān)資料,文中通過實例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2022-01-01Spring Boot自定義配置屬性源(PropertySource)
這篇文章主要介紹了Spring Boot自定義配置屬性源(PropertySource),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-06-06Spring Cloud Ribbon實現(xiàn)客戶端負載均衡的方法
本篇文章主要介紹了Spring Cloud Ribbon實現(xiàn)客戶端負載均衡的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-05-05