1、Java中拷貝的概念
在Java語言中,拷貝一個(gè)對(duì)象時(shí),有淺拷貝與深拷貝兩種
淺拷貝:只拷貝源對(duì)象的地址,所以新對(duì)象與老對(duì)象共用一個(gè)地址,當(dāng)該地址變化時(shí),兩個(gè)對(duì)象也會(huì)隨之改變。
深拷貝:拷貝對(duì)象的所有值,即使源對(duì)象發(fā)生任何改變,拷貝的值也不會(huì)變化。
在User類的基礎(chǔ)上,介紹兩種淺拷貝案列
User類:
@Data
public class User {
private String name;
private Integer age;
}
案列①:普通對(duì)象的淺拷貝
package com.shuizhu.study;
//淺拷貝案例1
public class Study01 {
public static void main(String[] args) {
User user1 = new User();
user1.setName("張三");
user1.setAge(18);
User user2 = user1;
System.out.println("user1未改變前,user2的名字為:" + user2.getName());
user1.setName("李四");
System.out.println("user1未改變前,user2的名字為:" + user2.getName());
}
}
結(jié)果:改變user1后,user2的值也隨之變化
案列②:List淺拷貝(這也是我們平時(shí)項(xiàng)目中,經(jīng)常遇到的情況)
package com.shuizhu.study;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
//Java淺拷貝案列2
public class Study02 {
public static void main(String[] args) {
List<User> list1 = new ArrayList<>();
User user1 = new User();
user1.setName("張三");
user1.setAge(18);
User user2 = new User();
user2.setName("李四");
user2.setAge(19);
list1.add(user1);
list1.add(user2);
//TODO 以下是開發(fā)中,經(jīng)常發(fā)生的淺拷貝
//方式1:通過new ArrayList方式,把list01拷貝給list02
List<User> list2 = new ArrayList<>(list1);
System.out.println("list1未改變前,list2的結(jié)果為:" + list2);
//方式2:通過addAll方法,把list01拷貝給list02
List<User> list3 = new ArrayList<>();
list3.addAll(list1);
System.out.println("list1未改變前,list3的結(jié)果為:" + list3);
//方式3:通過stream流的方式,把list01拷貝給list02
List<User> list4 = list1.stream().collect(Collectors.toList());
System.out.println("list1未改變前,list4的結(jié)果為:" + list4);
//改變list1集合中的user1對(duì)象
System.out.println("--------------------------------------------");
user1.setName("老六");
user1.setAge(78);
System.out.println("list1改變后,list2的結(jié)果為:" + list2);
System.out.println("list1改變后,list3的結(jié)果為:" + list3);
System.out.println("list1改變后,list4的結(jié)果為:" + list4);
}
}
結(jié)果:對(duì)List的3種拷貝,其實(shí)都是淺拷貝,當(dāng)源集合中對(duì)象發(fā)生改變時(shí),新的List也會(huì)隨之變化
2、常見的深拷貝方式
- 構(gòu)造函數(shù)方式(new的方式)
- 重寫clone方法
- Apache Commons Lang序列化
- Gson序列化
- Jackson序列化
2.1、構(gòu)造函數(shù)方式
這種方式就是創(chuàng)建一個(gè)新的對(duì)象,然后通過源對(duì)象的get方法與新對(duì)象set方法,把源對(duì)象的值復(fù)制新對(duì)象,這里就不再演示了。
缺點(diǎn):在拷貝的對(duì)象數(shù)量較少時(shí),可以使用,但是對(duì)象數(shù)量過多時(shí),會(huì)大大增加系統(tǒng)開銷,開發(fā)中應(yīng)避免使用。
2.2、重寫clone方法
步驟:
1>需要拷貝對(duì)象的類,去實(shí)現(xiàn)Cloneable接口
2>重寫clone方法
3>使用"對(duì)象.clone()"的方式進(jìn)行拷貝
根據(jù)上面的案列,進(jìn)行對(duì)應(yīng)的改造:
首先是User實(shí)體類 ,如下:
@Data
public class User implements Cloneable{
private String name;
private Integer age;
@Override
protected User clone() throws CloneNotSupportedException {
return (User) super.clone();
}
}
改造案列①:
package com.shuizhu.study;
//Java深拷貝案列
public class Study03 {
public static void main(String[] args) throws CloneNotSupportedException {
User user1 = new User();
user1.setName("張三");
user1.setAge(18);
User user2 = user1.clone();
System.out.println("user1未改變前,user2的名字為:" + user2.getName());
user1.setName("李四");
System.out.println("user1未改變前,user2的名字為:" + user2.getName());
}
}
結(jié)果:當(dāng)user1改變后,user2的值不會(huì)改變
改造案列②:List類型深拷貝
package com.shuizhu.study;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
//Java深拷貝案列
public class Study04 {
public static void main(String[] args) {
List<User> list1 = new ArrayList<>();
User user1 = new User();
user1.setName("張三");
user1.setAge(18);
User user2 = new User();
user2.setName("李四");
user2.setAge(19);
list1.add(user1);
list1.add(user2);
/
//通過clone方式,把list01拷貝給list02
List<User> list2 = new ArrayList<>();
//TODO 當(dāng)數(shù)據(jù)量多時(shí),建議使用對(duì)象的方式,把List當(dāng)做屬性,然后拷貝哦到一個(gè)新的對(duì)象中,從而不需要循環(huán),可以見Apache Commons Lang序列化深拷貝方式
list1.forEach(user->{
try {
list2.add(user.clone());
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
});
System.out.println("list1未改變前,list2的結(jié)果為:" + list2);
//改變list1集合中的user1對(duì)象
System.out.println("--------------------------------------------");
user1.setName("老六");
user1.setAge(78);
System.out.println("list1改變后,list2的結(jié)果為:" + list2);
}
}
結(jié)果:list1中的每個(gè)對(duì)象通過clone()添加list2中,當(dāng)list1中的對(duì)象改變時(shí),list2不會(huì)改變
2.3 、Apache Commons Lang序列化
步驟:
1>導(dǎo)入Commons包
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3:3.5</version>
</dependency>
2>實(shí)體類實(shí)現(xiàn)Serializable接口
@Data
public class User implements Serializable {
private String name;
private Integer age;
}
3>調(diào)用SerializationUtils工具類,實(shí)現(xiàn)深拷貝(注意:SerializationUtils不能直接拷貝List類型)
案列如下:
案列①:對(duì)象深拷貝
package com.shuizhu.study2;
import org.apache.commons.lang3.SerializationUtils;
//Apache Commons Lang序列化實(shí)現(xiàn)對(duì)象的深拷貝
public class Study01 {
public static void main(String[] args) {
User user1 = new User();
user1.setName("張三");
user1.setAge(18);
User user2 = SerializationUtils.clone(user1);
System.out.println("user1未改變前,user2的名字為:" + user2.getName());
user1.setName("李四");
System.out.println("user1改變后,user2的名字為:" + user2.getName());
}
}
結(jié)果:user1的改變不會(huì)導(dǎo)致user2的改變,從而實(shí)現(xiàn)深拷貝
?
案列②:List類型深拷貝
(1)改造開始,我們先創(chuàng)建一個(gè)專門用于拷貝List<User>類型的實(shí)體類
package com.shuizhu.study2;
import java.io.Serializable;
import java.util.List;
/**
* @author 睡竹
* @date 2022/12/10
* 用于深拷貝時(shí),不需要去遍歷List<User>集合,只需要拷貝UserCopyDTO 對(duì)象就可以
* 獲取到新的List<User>集合
*/
@Data
public class UserCopyDTO implements Serializable {//必須實(shí)現(xiàn)Serializable接口
private List<User> users;
}
(2)拷貝List類型
package com.shuizhu.study2;
import org.apache.commons.lang3.SerializationUtils;
import java.util.ArrayList;
import java.util.List;
//Apache Commons Lang序列化實(shí)現(xiàn)List的深拷貝
public class Study02 {
public static void main(String[] args) {
List<User> list1 = new ArrayList<>();
User user1 = new User();
user1.setName("張三");
user1.setAge(18);
User user2 = new User();
user2.setName("李四");
user2.setAge(19);
list1.add(user1);
list1.add(user2);
//使用UserCopyDTO對(duì)象,專門用于拷貝List<User>類型數(shù)據(jù),不需要再去遍歷list1
UserCopyDTO userCopyDTO = new UserCopyDTO();
userCopyDTO.setUsers(list1);
//通過Apache Commons Lang序列化方式,把list01拷貝給list02
UserCopyDTO clone = SerializationUtils.clone(userCopyDTO);
List<User> list2 = clone.getUsers();
System.out.println("list1未改變前,list2的結(jié)果為:" + list2);
//改變list1集合中的user1對(duì)象
System.out.println("--------------------------------------------");
user1.setName("老六");
user1.setAge(78);
System.out.println("list1改變后,list2的結(jié)果為:" + list2);
}
}
結(jié)果:
?2.4、Gson序列化
步驟:
1、導(dǎo)入Gson依賴
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.5</version>
</dependency>
2>創(chuàng)建Gson對(duì)象,使用該對(duì)象進(jìn)行深拷貝(實(shí)體類不再需要實(shí)現(xiàn)Serializable接口)
案例如下:只演示對(duì)象的深拷貝,LIst類型的深拷貝與之前的流程是相似的
package com.shuizhu.study3;
import com.google.gson.Gson;
//Gson序列化實(shí)現(xiàn)對(duì)象的深拷貝
public class Study01 {
public static void main(String[] args) {
User user1 = new User();
user1.setName("張三");
user1.setAge(18);
Gson gson = new Gson();
User user2 = gson.fromJson(gson.toJson(user1), User.class);
System.out.println("user1未改變前,user2的名字為:" + user2.getName());
user1.setName("李四");
System.out.println("user1改變后,user2的名字為:" + user2.getName());
}
}
重點(diǎn):
結(jié)果:
?
?2.5、Jackson序列化
該方式與Gson原理、使用方式相似,但是Jackson序列化深拷貝,要求拷貝的對(duì)象必須有無參構(gòu)造函數(shù)
步驟:
1>導(dǎo)入Jackson依賴
<dependency>
<groupId>com.fasterxml.jackson</groupId>
<artifactId>core</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson</groupId>
<artifactId>databind</artifactId>
<version>2.2.2</version>
</dependency>
2>創(chuàng)建ObjectMapper對(duì)象,進(jìn)行深拷貝(用法與Gson一致)
package com.shuizhu.study4;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
//Jackson序列化實(shí)現(xiàn)對(duì)象的深拷貝
public class Study01 {
public static void main(String[] args) {
User user1 = new User();
user1.setName("張三");
user1.setAge(18);
ObjectMapper mapper = new ObjectMapper();
User user2 = null;
try {
user2 = mapper.readValue(mapper.writeValueAsString(user1), User.class);
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("user1未改變前,user2的名字為:" + user2.getName());
user1.setName("李四");
System.out.println("user1改變后,user2的名字為:" + user2.getName());
}
}
重點(diǎn):
結(jié)果:
?
3、總結(jié)
方式 | 優(yōu)點(diǎn) | 缺點(diǎn) |
構(gòu)造函數(shù) | 1. 底層實(shí)現(xiàn)簡單 2. 不需要引入第三方包 3. 系統(tǒng)開銷小 4. 對(duì)拷貝類沒有要求,不需要實(shí)現(xiàn)額外接口和方法 | 1. 可用性差,每次新增成員變量都需要新增新的拷貝構(gòu)造函數(shù) |
重載clone()方法 | 1. 底層實(shí)現(xiàn)較簡單 2. 不需要引入第三方包 3. 系統(tǒng)開銷小 追求性能的可以采用該方式 |
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)開銷 |
Gson序列化 | 1. 可用性強(qiáng),新增成員變量不需要修改拷貝方法 2. 對(duì)拷貝類沒有要求,不需要實(shí)現(xiàn)額外接口和方法 | 1. 底層實(shí)現(xiàn)復(fù)雜 2. 需要引入Gson第三方JAR包 3. 序列化與反序列化存在一定的系統(tǒng)開銷 |
Jackson序列化 | 1. 可用性強(qiáng),新增成員變量不需要修改拷貝方法 | 1. 底層實(shí)現(xiàn)復(fù)雜 2. 需要引入Jackson第三方JAR包 3. 拷貝類(包括其成員變量)需要實(shí)現(xiàn)默認(rèn)的無參構(gòu)造函數(shù) 4. 序列化與反序列化存在一定的系統(tǒng)開銷 |
?文章來源地址http://www.zghlxwxcb.cn/news/detail-781085.html
?文章來源:http://www.zghlxwxcb.cn/news/detail-781085.html
?
?
?
到了這里,關(guān)于Java對(duì)象深拷貝詳解(List深拷貝)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!