Java对象深拷贝详解(List深拷贝)
1、Java中拷贝的概念
在Java语言中,拷贝一个对象时,有浅拷贝与深拷贝两种
浅拷贝:只拷贝源对象的地址,所以新对象与老对象共用一个地址,当该地址变化时,两个对象也会随之改变。
深拷贝:拷贝对象的所有值,即使源对象发生任何改变,拷贝的值也不会变化。
在User类的基础上,介绍两种浅拷贝案列
User类:
@Data
public class User {private String name;private Integer age;
}
案列①:普通对象的浅拷贝
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());}
}
结果:改变user1后,user2的值也随之变化
案列②:List浅拷贝(这也是我们平时项目中,经常遇到的情况)
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 以下是开发中,经常发生的浅拷贝//方式1:通过new ArrayList方式,把list01拷贝给list02List<User> list2 = new ArrayList<>(list1);System.out.println("list1未改变前,list2的结果为:" + list2);//方式2:通过addAll方法,把list01拷贝给list02List<User> list3 = new ArrayList<>();list3.addAll(list1);System.out.println("list1未改变前,list3的结果为:" + list3);//方式3:通过stream流的方式,把list01拷贝给list02List<User> list4 = list1.stream().collect(Collectors.toList());System.out.println("list1未改变前,list4的结果为:" + list4);//改变list1集合中的user1对象System.out.println("--------------------------------------------");user1.setName("老六");user1.setAge(78);System.out.println("list1改变后,list2的结果为:" + list2);System.out.println("list1改变后,list3的结果为:" + list3);System.out.println("list1改变后,list4的结果为:" + list4);}
}
结果:对List的3种拷贝,其实都是浅拷贝,当源集合中对象发生改变时,新的List也会随之变化
2、常见的深拷贝方式
- 构造函数方式(new的方式)
- 重写clone方法
- Apache Commons Lang序列化
- Gson序列化
- Jackson序列化
2.1、构造函数方式
这种方式就是创建一个新的对象,然后通过源对象的get方法与新对象set方法,把源对象的值复制新对象,这里就不再演示了。
缺点:在拷贝的对象数量较少时,可以使用,但是对象数量过多时,会大大增加系统开销,开发中应避免使用。
2.2、重写clone方法
步骤:
1>需要拷贝对象的类,去实现Cloneable接口
2>重写clone方法
3>使用"对象.clone()"的方式进行拷贝
根据上面的案列,进行对应的改造:
首先是User实体类 ,如下:
@Data
public class User implements Cloneable{private String name;private Integer age;@Overrideprotected 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());}
}
结果:当user1改变后,user2的值不会改变
改造案列②: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拷贝给list02List<User> list2 = new ArrayList<>(); //TODO 当数据量多时,建议使用对象的方式,把List当做属性,然后拷贝哦到一个新的对象中,从而不需要循环,可以见Apache Commons Lang序列化深拷贝方式list1.forEach(user->{try {list2.add(user.clone());} catch (CloneNotSupportedException e) {e.printStackTrace();}});System.out.println("list1未改变前,list2的结果为:" + list2);//改变list1集合中的user1对象System.out.println("--------------------------------------------");user1.setName("老六");user1.setAge(78);System.out.println("list1改变后,list2的结果为:" + list2);}
}
结果:list1中的每个对象通过clone()添加list2中,当list1中的对象改变时,list2不会改变
2.3 、Apache Commons Lang序列化
步骤:
1>导入Commons包
<dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3:3.5</version>
</dependency>
2>实体类实现Serializable接口
@Data
public class User implements Serializable {private String name;private Integer age;
}
3>调用SerializationUtils工具类,实现深拷贝(注意:SerializationUtils不能直接拷贝List类型)
案列如下:
案列①:对象深拷贝
package com.shuizhu.study2;
import org.apache.commons.lang3.SerializationUtils;//Apache Commons Lang序列化实现对象的深拷贝
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());}
}
结果:user1的改变不会导致user2的改变,从而实现深拷贝
案列②:List类型深拷贝
(1)改造开始,我们先创建一个专门用于拷贝List<User>类型的实体类
package com.shuizhu.study2;import java.io.Serializable;
import java.util.List;/*** @author 睡竹* @date 2022/12/10* 用于深拷贝时,不需要去遍历List<User>集合,只需要拷贝UserCopyDTO 对象就可以* 获取到新的List<User>集合*/
@Data
public class UserCopyDTO implements Serializable {//必须实现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序列化实现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对象,专门用于拷贝List<User>类型数据,不需要再去遍历list1 UserCopyDTO userCopyDTO = new UserCopyDTO();userCopyDTO.setUsers(list1);//通过Apache Commons Lang序列化方式,把list01拷贝给list02UserCopyDTO clone = SerializationUtils.clone(userCopyDTO);List<User> list2 = clone.getUsers();System.out.println("list1未改变前,list2的结果为:" + list2);//改变list1集合中的user1对象System.out.println("--------------------------------------------");user1.setName("老六");user1.setAge(78);System.out.println("list1改变后,list2的结果为:" + list2);}
}
结果:
2.4、Gson序列化
步骤:
1、导入Gson依赖
<dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId><version>2.8.5</version>
</dependency>
2>创建Gson对象,使用该对象进行深拷贝(实体类不再需要实现Serializable接口)
案例如下:只演示对象的深拷贝,LIst类型的深拷贝与之前的流程是相似的
package com.shuizhu.study3;
import com.google.gson.Gson;//Gson序列化实现对象的深拷贝
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());}
}
重点:
结果:
2.5、Jackson序列化
该方式与Gson原理、使用方式相似,但是Jackson序列化深拷贝,要求拷贝的对象必须有无参构造函数
步骤:
1>导入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>创建ObjectMapper对象,进行深拷贝(用法与Gson一致)
package com.shuizhu.study4;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;//Jackson序列化实现对象的深拷贝
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());}
}
重点:
结果:
3、总结
方式 | 优点 | 缺点 |
构造函数 | 1. 底层实现简单 2. 不需要引入第三方包 3. 系统开销小 4. 对拷贝类没有要求,不需要实现额外接口和方法 | 1. 可用性差,每次新增成员变量都需要新增新的拷贝构造函数 |
重载clone()方法 |
1. 底层实现较简单 2. 不需要引入第三方包 3. 系统开销小 追求性能的可以采用该方式 |
1. 可用性较差,每次新增成员变量可能需要修改clone()方法 2. 拷贝类(包括其成员变量)需要实现Cloneable接口 |
Apache Commons Lang序列化 | 1. 可用性强,新增成员变量不需要修改拷贝方法 | 1. 底层实现较复杂 2. 需要引入Apache Commons Lang第三方JAR包 3. 拷贝类(包括其成员变量)需要实现Serializable接口 4. 序列化与反序列化存在一定的系统开销 |
Gson序列化 | 1. 可用性强,新增成员变量不需要修改拷贝方法 2. 对拷贝类没有要求,不需要实现额外接口和方法 | 1. 底层实现复杂 2. 需要引入Gson第三方JAR包 3. 序列化与反序列化存在一定的系统开销 |
Jackson序列化 | 1. 可用性强,新增成员变量不需要修改拷贝方法 | 1. 底层实现复杂 2. 需要引入Jackson第三方JAR包 3. 拷贝类(包括其成员变量)需要实现默认的无参构造函数 4. 序列化与反序列化存在一定的系统开销 |
Java对象深拷贝详解(List深拷贝)相关推荐
- 64位JVM的Java对象头详解
关注"Java艺术"一起来充电吧! 我们编写一个Java类,编译后会生成.class文件,当类加载器将class文件加载到jvm时,会生成一个Klass类型的对象(c++),称为类 ...
- Java对象序列化详解6,Java对象的序列化与反序列化详解
把对象转换为字节序列的过程称为对象的序列化,把字节序列恢复为对象的过程称为对象的反序列化. 对象的序列化主要有两种途径: Ⅰ . 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中 Ⅱ. 在网 ...
- Java对象序列化详解
下面的文章在公众号作了更新:点击查看最新文章 可识别二维码查看更多最新文章: 写在前面 Java对象是在JVM中生成的,如果需要远程传输或保存到硬盘上,就需要将Java对象转换成可传输的文件流. 市面 ...
- Java对象初始化详解
在Java中,一个对象在可以被使用之前必须要被正确地初始化,这一点是Java规范规定的.本文试图对Java如何执行对象的初始化做一个详细深入地介绍(与对象初始化相同,类在被加载之后也是需要初始化的,本 ...
- java对象克隆详解
概述: 当我们new一个对象时,其中的属性就会被初始化, 那么想要保存刚开始初始化的值就靠clone方法来实现, 平时我们最常见的是一个对象的引用指向另一个对象,并不是创建了两个对象. Person ...
- Java 对象排序详解
很难想象有Java开发人员不曾使用过Collection框架.在Collection框架中,主要使用的类是来自List接口中的ArrayList,以及来自Set接口的HashSet.TreeSet,我 ...
- Java内存溢出详解之Tomcat配置
Java内存溢出详解 转自:http://elf8848.iteye.com/blog/378805 一.常见的Java内存溢出有以下三种: 1. java.lang.OutOfMemoryError ...
- java基础(十三)-----详解内部类——Java高级开发必须懂的
java基础(十三)-----详解内部类--Java高级开发必须懂的 目录 为什么要使用内部类 内部类基础 静态内部类 成员内部类 成员内部类的对象创建 继承成员内部类 局部内部类 推荐博客 匿名内部 ...
- Java类加载机制详解【java面试题】
Java类加载机制详解[java面试题] (1)问题分析: Class文件由类装载器装载后,在JVM中将形成一份描述Class结构的元信息对象,通过该元信息对象可以获知Class的结构信息:如构造函数 ...
- Java 线程池详解学习:FixedThreadPool,CachedThreadPool,ScheduledThreadPool...
Java常用的线程池有FixedThreadPool和CachedThreadPool,我们可以通过查看他们的源码来进行学习. Java的源码下载参考这篇文章:Java源码下载和阅读(JDK1.8) ...
最新文章
- jQuery 人脸识别插件,支持图片和视频
- python中函数重载和重写
- 操作系统(十三)处理机调度的概念、层次
- outdated: 29.Blitter Function, RAW Texture Loading
- 数据结构——树与二叉树
- @description iPhoneX炫彩渐变背景实现
- 加载一张图片到ImageView到底占据多少内存
- Kubernetes(十七) 基于NFS的动态存储申请
- 在柱状图中找最大矩形——O(n)时间复杂度java实现
- arcgis api for javascript中使用proxy.jsp
- windows内置的linux安卓驱动多系统摆脱虚拟机(下)
- JPanle组件按钮 表格布局
- 基于树莓派的Data Matrix decode
- 计算机文献检索过程,计算机文献检索的基本方法与策略
- php 语句以句号结尾,短句末尾是否用句号
- 各大互联网企业Java面试题汇总,看我如何成功拿到百度的offer
- android中关于keytool 错误:java.lang.Exception:密钥库文件不存在: 解决步骤
- Echarts y轴高度设置(宽度铺满整个父级高度)
- Flink 实时数仓伪分布虚拟机 (所有组件部署完成)
- 从0基础到车载测试工程师,薪资11K,肯拼搏的人,总会有所收获
热门文章
- USB 2.0 AWM 2725 24AWG/1PR AND 24AWG/2C
- MessageBox常用方法
- 没有终结点在侦听可以接受消息的 U9WorkflowService
- TcaplusDB君 · 行业新闻汇编(8月9日)
- 切换界面之后 SwipeRefreshLayout 动画效果暂停,以及不消失的解决方案
- 中冠百年|短期理财和长期理财哪个好
- MaxCompute(DataIDE)数据核查
- java sqlserver ssl_如何解决无法通过SSL加密与SQLServer建立连接
- Android Studio 圆形头像带边框的
- 牛客月赛19-皇家烈焰-(多状态dp)