为什么需要深层复制

Object 的 clone() 方法是浅层复制(但是 native 很高效)。
另外,Java 提供了数组和集合的复制方法,分别是 Arrays.copy() 和 Collections.copy() 方法。
前者实际上使用了 System.arraycopy() 方法,两者其实也是浅层复制,过程类似于下面的 for 循环:

for(int i=0; i<len; i++){
  dest[i] = src[i];
}

所以当数组或集合中元素是对象时,只是做了引用的复制,指向的还是堆中同一个对象。

一般有两种深层复制方案

1)实现 Cloneable 接口

包装 Object.clone() ,根据属性类型深度 clone。

这种方法,使用了 Object.clone() ,优点是 native 方法性能好,缺点是实现太繁琐。

/**
* 0 实现 Cloneable 接口
* 1 包装 super.clone(),提供 public 方法
* 2 默认的是浅层复制
* @author Super
*
*/
public class shallowCloneTest { public static void main(String[] args) {
Resource0 r0 = new Resource0(0, "资源1号");
Resource0 r1 = new Resource0(1, "内部资源");
r0.setInnerResource(r1); //验证克隆
Resource0 r2 = r0.shallowClone();
System.out.println(r0);
System.out.println(r2);
System.out.println(r1==r2); //false //验证浅度克隆
r2.getInnerResource().setId(7);
System.out.println(r1); //受影响了
} } /**
* 深层复制方案一:包装 clone,引用变量继续 clone
* @author Super
*
*/
public class DeepCloneTest1 { public static void main(String[] args) {
Resource0 r0 = new Resource0(0, "资源1号");
Resource0 r1 = new Resource0(1, "内部资源");
r0.setInnerResource(r1); //验证克隆
Resource0 r2 = r0.deepClone();
System.out.println(r0);
System.out.println(r2);
System.out.println(r1==r2); //false //验证深度度克隆
r2.getInnerResource().setId(7);
System.out.println(r1); //不受影响
} } public class Resource0 extends BaseVo implements Cloneable { private static final long serialVersionUID = 1L; private Integer id;
private String name;
private Resource0 innerResource; public Resource0(Integer id, String name) {
super();
this.id = id;
this.name = name;
} public Integer getId() {
return id;
} public void setId(Integer id) {
this.id = id;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public Resource0 getInnerResource() {
return innerResource;
} public void setInnerResource(Resource0 innerResource) {
this.innerResource = innerResource;
} /**
* 浅层复制
* @return
*/
public Resource0 shallowClone(){
Resource0 r = null;
try {
r = (Resource0)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return r;
} /**
* 深层复制
* @return
*/
public Resource0 deepClone(){
Resource0 r = null;
try {
r = (Resource0)super.clone();
r.innerResource = (Resource0) innerResource.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return r;
} @Override
public String toString() {
return "Resource0 [id=" + id + ", name=" + name + ", innerResource="
+ innerResource + "]";
}
}

2)对象序列化输入输出

这种方法,强大且简单,先写到内存,再读出来。

PS:单例对象最好也关注下是否有被序列化复制的风险。

/**
* 深层复制方案2:输入输出序列化
* @author Super
*
*/
public class DeepCloneTest2 { public static void main(String[] args) {
Resource0 r0 = new Resource0(0, "资源1号");
Resource0 r1 = new Resource0(1, "内部资源");
r0.setInnerResource(r1); //验证克隆
Resource0 r2 = (Resource0) IOUtil.deepClone(r0);
System.out.println(r0);
System.out.println(r2);
System.out.println(r1==r2); //false //验证深度度克隆
r2.getInnerResource().setId(7);
System.out.println(r1); //不受影响
} } /**
* 深层复制序列化 vo
* @param src
* @return dest
* @throws IOException
* @throws ClassNotFoundException
*/
public static BaseVo deepClone(BaseVo src) {
ByteArrayOutputStream bo = null;
ObjectOutputStream out = null;
ObjectInputStream in = null;
BaseVo dest = null;
try{
try{
//对象写入内存
bo = new ByteArrayOutputStream();
out = new ObjectOutputStream(bo);
out.writeObject(src);
//从内存中读回来
in = new ObjectInputStream(new ByteArrayInputStream(bo.toByteArray()));
dest = (BaseVo) in.readObject();
}finally{
//使用 finally 关闭资源
if(in!=null){
in.close();
}
if(out!=null){
out.close();
}
if(bo!=null){
bo.close();
}
}
//使用 catch 块统一捕捉资源
} catch(IOException | ClassNotFoundException ex){
ex.printStackTrace();
} return dest;
}

参考阅读:

https://www.jianshu.com/p/7aaaf884cc44

Java深层复制方式的更多相关文章

  1. java数组对象的浅层复制与深层复制

    实际上,java中数组对象的浅层复制只是复制了对象的引用(参考),而深层复制的才是对象所代表的值.

  2. java多种文件复制方式以及效率比较

    1.背景 java复制文件的方式其实有很多种,可以分为 传统的字节流读写复制FileInputStream,FileOutputStream,BufferedInputStream,BufferedO ...

  3. Java 对象复制

    Java 对象的一共有 3 种复制对象的方式. 1.直接赋值 (引用复制 ),此种复制方式比较常用. 诸如 A  a = b ;  a 是直接复制了b的引用 ,也就是说它俩指向的是同一个对象. 此时 ...

  4. js中的深层复制

    同java一样,数据的复制,不小心就是一个浅复制,莫名其妙的数据就被修改了,所以我们需要考虑深层复制的问题.这里提供一个深层复制的方法. 1.脚本 /** * 深层复制 */ cloneObject ...

  5. java 四种方式实现字符流文件的拷贝对比

    将D:\\应用软件\\vm.exe  拷贝到C:\\vm.exe   四种方法耗费时间对比  4>2>3>1 package Copy; import java.io.Buffere ...

  6. java 传参方式--值传递还是引用传递

    java 传参方式--值传递还是引用传递 参数是按值而不是按引用传递的说明 Java 应用程序有且仅有的一种参数传递机制,即按值传递.写它是为了揭穿普遍存在的一种神话,即认为 Java 应用程序按引用 ...

  7. Java 使用线程方式Thread和Runnable,以及Thread与Runnable的区别

    一. java中实现线程的方式有Thread和Runnable Thread: public class Thread1 extends Thread{ @Override public void r ...

  8. java通过jni方式获取硬盘序列号(windows,linux)

    linux系统java通过jni方式获取硬盘序列号 http://blog.csdn.net/starter110/article/details/8186788 使用jni在windows下读取硬盘 ...

  9. Java 初始化字段方式和顺序

    Java 初始化字段方式和顺序: 类加载时直接初始化静态字段; 类加载时调用静态方法初始化静态字段; 实例化对象时,在调用构造函数之前代码块中初始化字段; 实例化对象时,在调用构造函数之时初始化字段; ...

随机推荐

  1. spring中获取容器中的Bean为什么前转成接口而不是实现类

    简单介绍一下上下文,userService是服务层接口有一个save方法,userServiceImpl是该接口的实现类重写了save方法. applicationContext.xml如图: 后台代 ...

  2. 关于HTML的引入CSS文件问题

    一 html代码引用外部css文件时若css文件在本文件的父目录下的其他目录下,可使用绝对路径.此时路径要写为  “ ../ ”形式,如在tomcat下建立一个test文件,在该文件中建立两个文件 夹 ...

  3. Spring WebClient vs. RestTemplate

    1. 简介 本教程中,我们将对比 Spring 的两种 Web 客户端实现 -- RestTemplate 和 Spring 5 中全新的 Reactive 替代方案 WebClient. 2. 阻塞 ...

  4. Thinkphp 5.1.7 parseData缺陷导致insert/update注入 分析

    目录 环境搭建 分析 参考 环境搭建 $ composer create-project topthink/think thinkphp-5.1.7 修改composer.json 5.1.* =&g ...

  5. VSTO之PowerPoint(PPT)插件开发常用API汇总

    VSTO简介 VSTO(Visual Studio Tools for Office )是VBA的替代,使得开发Office应用程序更加简单,并且用VSTO来开发office应用程序可以使用Visua ...

  6. Extjs的textfield的颜色设置和出现的问题笔记

    Ext.getCmp('alarmsLevelVal').setFieldStyle('background-color:#ff0000;background-p_w_picpath: none; ' ...

  7. 实时同步lsyncd

    实时同步lsyncd 1 lsyncd 1.1 lsyncd 简介 Lsyncd使用文件系统事件接口(inotify或fsevents)来监视对本地文件和目录的更改.Lsyncd将这些事件整理几秒钟, ...

  8. 【有容云案例系列】基于Jenkins和Kubernetes的CI工作流

    嘉宾介绍 黄文俊 有容云资深系统架构师 主要负责容器云平台产品架构及设计. 8年工作经验, 有着企业级存储, 云计算解决方案相关理解. 关注于微服务设计思考, 开发流程优化, docker及kuber ...

  9. 利用Idea重构功能及Java8语法特性——优化深层嵌套代码

    当遇到深层嵌套代码,如for,if,lambda表达式或内部类及这些代码的组合,这时我们可以通过Java 8的语法特性来进行优化. 下面的代码是一个嵌套循环的示例. public MappedFiel ...

  10. vue过滤器的使用

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...