为什么需要深层复制

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. django第一次(转自刘江大佬)

    下面的模型定义了一个“人”,它具有first_name和last_name字段: from django.db import models class Person(models.Model): fi ...

  2. Java面向对象16种原则

    一   类的设计原则   1 依赖倒置原则-Dependency Inversion Principle (DIP) 2 里氏替换原则-Liskov Substitution Principle (L ...

  3. [重磅开源] 比SingleR更适合的websocket 即时通讯组件---ImCore开源了

    有感而发 为什么说 SignalR 不合适做 IM? IM 的特点必定是长连接,轮训的功能用不上. 因为它是双工通讯的设计,用hub.invoke发送命令给服务端处理业务,其他就和 ajax 差不多, ...

  4. JSON在线格式化 jsoneditor使用

    const placeholder = { string: 'hello world!', boolean: true, color: '#6c928c', number: 123, null: nu ...

  5. .net持续集成测试篇之Nunit 测试配置

    系列目录 在开始之前我们先看一个陷阱 用到的Person类如下 public class Person:IPerson { public string Name { get; set; } publi ...

  6. extjs4 表单验证自定义

    extjs4 在验证上面支持的也特别好,他可以使用自带的格式验证,也可以自定义验证 比如:正则验证,密码重复填写对比验证,以及 调用后台方法验证,下面将验证方法统一写出以供参考 function lo ...

  7. 消息中间件-activemq消息机制和持久化介绍(三)

    前面一节简单学习了activemq的使用,我们知道activemq的使用方式非常简单有如下几个步骤: 创建连接工厂 创建连接 创建会话 创建目的地 创建生产者或消费者 生产或消费消息 关闭生产或消费者 ...

  8. [原创实践]IBM thinkpad T61制作和使用recovery光盘进行出厂系统恢复

    制作系统恢复盘 之前制作了系统恢复光盘,包含Product recovery 光盘1和光盘2,rescure and recovery光盘. 联想笔记本XP系统有一个硬盘分区是用来做恢复的,双击硬盘即 ...

  9. Servlet生成验证码并进行账号密码和验证码的验证登陆!

    前言: 人不是生来就懂事的,在编程的世界也是一样,想想在大一的时候我还是那个连输出Hello World!都不会的小孩子是,现在我已经可以编出属于我自己的小程序了.编程其实并不可怕,可怕的是你不去编. ...

  10. 一文了解:Redis过期键删除策略

    Redis过期键删除策略 Redis中所有的键都可以设置过期策略,就像是所有的键都可以上"生死簿",上了生死簿的键到时间后阎王就会叉掉这个键.同一时间大量的键过期,阎王就会忙不过来 ...