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、常见的深拷贝方式

  1. 构造函数方式(new的方式)
  2. 重写clone方法
  3. Apache Commons Lang序列化
  4. Gson序列化
  5. 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深拷贝)相关推荐

  1. 64位JVM的Java对象头详解

    关注"Java艺术"一起来充电吧! 我们编写一个Java类,编译后会生成.class文件,当类加载器将class文件加载到jvm时,会生成一个Klass类型的对象(c++),称为类 ...

  2. Java对象序列化详解6,Java对象的序列化与反序列化详解

    把对象转换为字节序列的过程称为对象的序列化,把字节序列恢复为对象的过程称为对象的反序列化. 对象的序列化主要有两种途径: Ⅰ . 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中 Ⅱ.  在网 ...

  3. Java对象序列化详解

    下面的文章在公众号作了更新:点击查看最新文章 可识别二维码查看更多最新文章: 写在前面 Java对象是在JVM中生成的,如果需要远程传输或保存到硬盘上,就需要将Java对象转换成可传输的文件流. 市面 ...

  4. Java对象初始化详解

    在Java中,一个对象在可以被使用之前必须要被正确地初始化,这一点是Java规范规定的.本文试图对Java如何执行对象的初始化做一个详细深入地介绍(与对象初始化相同,类在被加载之后也是需要初始化的,本 ...

  5. java对象克隆详解

    概述: 当我们new一个对象时,其中的属性就会被初始化, 那么想要保存刚开始初始化的值就靠clone方法来实现, 平时我们最常见的是一个对象的引用指向另一个对象,并不是创建了两个对象. Person ...

  6. Java 对象排序详解

    很难想象有Java开发人员不曾使用过Collection框架.在Collection框架中,主要使用的类是来自List接口中的ArrayList,以及来自Set接口的HashSet.TreeSet,我 ...

  7. Java内存溢出详解之Tomcat配置

    Java内存溢出详解 转自:http://elf8848.iteye.com/blog/378805 一.常见的Java内存溢出有以下三种: 1. java.lang.OutOfMemoryError ...

  8. java基础(十三)-----详解内部类——Java高级开发必须懂的

    java基础(十三)-----详解内部类--Java高级开发必须懂的 目录 为什么要使用内部类 内部类基础 静态内部类 成员内部类 成员内部类的对象创建 继承成员内部类 局部内部类 推荐博客 匿名内部 ...

  9. Java类加载机制详解【java面试题】

    Java类加载机制详解[java面试题] (1)问题分析: Class文件由类装载器装载后,在JVM中将形成一份描述Class结构的元信息对象,通过该元信息对象可以获知Class的结构信息:如构造函数 ...

  10. Java 线程池详解学习:FixedThreadPool,CachedThreadPool,ScheduledThreadPool...

    Java常用的线程池有FixedThreadPool和CachedThreadPool,我们可以通过查看他们的源码来进行学习. Java的源码下载参考这篇文章:Java源码下载和阅读(JDK1.8) ...

最新文章

  1. jQuery 人脸识别插件,支持图片和视频
  2. python中函数重载和重写
  3. 操作系统(十三)处理机调度的概念、层次
  4. outdated: 29.Blitter Function, RAW Texture Loading
  5. 数据结构——树与二叉树
  6. @description iPhoneX炫彩渐变背景实现
  7. 加载一张图片到ImageView到底占据多少内存
  8. Kubernetes(十七) 基于NFS的动态存储申请
  9. 在柱状图中找最大矩形——O(n)时间复杂度java实现
  10. arcgis api for javascript中使用proxy.jsp
  11. windows内置的linux安卓驱动多系统摆脱虚拟机(下)
  12. JPanle组件按钮 表格布局
  13. 基于树莓派的Data Matrix decode
  14. 计算机文献检索过程,计算机文献检索的基本方法与策略
  15. php 语句以句号结尾,短句末尾是否用句号
  16. 各大互联网企业Java面试题汇总,看我如何成功拿到百度的offer
  17. android中关于keytool 错误:java.lang.Exception:密钥库文件不存在: 解决步骤
  18. Echarts y轴高度设置(宽度铺满整个父级高度)
  19. Flink 实时数仓伪分布虚拟机 (所有组件部署完成)
  20. 从0基础到车载测试工程师,薪资11K,肯拼搏的人,总会有所收获

热门文章

  1. USB 2.0 AWM 2725 24AWG/1PR AND 24AWG/2C
  2. MessageBox常用方法
  3. 没有终结点在侦听可以接受消息的 U9WorkflowService
  4. TcaplusDB君 · 行业新闻汇编(8月9日)
  5. 切换界面之后 SwipeRefreshLayout 动画效果暂停,以及不消失的解决方案
  6. 中冠百年|短期理财和长期理财哪个好
  7. MaxCompute(DataIDE)数据核查
  8. java sqlserver ssl_如何解决无法通过SSL加密与SQLServer建立连接
  9. Android Studio 圆形头像带边框的
  10. 牛客月赛19-皇家烈焰-(多状态dp)