way-to-architect
  • 前言
  • Java
    • Java关键字
      • Java中四种修饰符的限制范围
      • static和final
    • 容器
      • 容器概述
        • 容器:综述
        • Iterator原理及实现
        • fast-fail机制
        • 比较器Comparator
        • Collections工具类
      • List
        • List综述
        • ArrayList原理分析
        • ArrayList在循环过程中删除元素的问题
        • 常用的小技巧
        • CopyOnWrite
      • Set
        • Set综述
        • HashSet
        • LinkedHashSet
        • TreeSet
      • Queue
        • Queue综述
        • ArrayBlockingQueue实现原理
        • LinkedBlockingQueue实现原理
        • 高性能无锁队列Disruptor
      • Map
        • Map综述
        • HashMap
          • HashMap实现原理
          • HashMap中的位运算
          • HashMap其他问题
        • LinkedHashMap
        • TreeMap
        • ConcurrentHashMap
          • ConcurrentHashMap实现原理JDK1.7
          • ConcurrentHashMap实现原理JDK1.8
        • ConcurrentSkipListMap
        • Map中key和value的null的问题
    • 线程
      • 线程
        • 创建线程
        • 线程状态及切换
        • 线程中断的理解
        • 几种方法的解释
        • 用户线程与守护线程
        • 线程组ThreadGroup
      • 线程池
        • 线程池工作原理及创建
        • Executor
        • 如何确保同一属性的任务被同一线程执行
      • ThreadLocal
        • ThreadLocal原理
        • ThreadLocal之父子线程传值
        • InheritableThreadLocal
      • 同步与锁
        • 线程安全与锁优化
        • synchronize关键字
        • Lock
          • 队列同步器
            • 同步状态的获取与释放
            • 使用方式
            • 示例:Mutex
            • 示例:TwinsLock
          • 重入锁和读写锁
          • LockSupport
          • Condition
          • 并发工具类
        • CAS
          • CAS的理解
          • Java中原子操作类
        • 3个经典同步问题
      • fork/join的理解
    • I/O
      • I/O概述
        • 磁盘I/O与网络I/O
        • 主要接口
        • 输入流和输出流的使用示例
        • InputStream的重复读
        • BufferdxxxxStream
        • Serailizable
        • File常用方法
        • Files和Path
        • RandomAccessFile
        • 通过零拷贝实现有效数据传输
        • 正确地处理文件
      • NIO基础
      • NIO2
      • Netty
        • Java I/O的演进之路
        • 为什么是Netty
        • 更多
      • I/O调优
    • 异常
      • 异常体系及为什么要有这种异常设计
      • 多catch的执行情况
      • try catch finally 与reture
      • 异常处理的误区
      • Preconditions:方法入参校验工具
    • 枚举
      • 常见用法
      • 枚举类在序列化中的问题
    • 注解
      • 概述
      • Spring中的组合注解的条件注解
      • 常用注解
        • JSR-330标准注解
    • 反射
      • 概述
      • 内部类的反射
      • 反射中需要注意的地方
    • 流程控制
      • switch case without break
      • Java: for(;;) vs. while(true)
    • JVM
      • JVM内存结构
      • Java内存模型
      • 垃圾收集器和内存分配策略
      • 四种引用类型区别及何时回收
      • 类文件结构
      • 类初始化顺序
      • 类加载机制
      • 虚拟机执行引擎
      • 逃逸分析
      • JVM常用配置
      • GC日志分析
      • Java8 JVM 参数解读
      • 垃圾收集器和内存分配策略
    • 面向对象
      • Object类中的方法
      • Class类中的方法
      • 值传递还是引用传递?
      • 接口和抽象类的区别
      • 深拷贝和浅拷贝
      • Integer.parseInt()与Interger.valueof()
      • hashCode()与equal()
      • String
        • String池化及intern()方法的作用
        • 关于字符串
    • 序列化
      • Java序列化的方式有哪些?
    • 新特性
      • 流 Stream
        • Stream是什么
        • Stream API详解
        • Stream进阶
        • 流编程
        • 其他事项
      • lambda表达式
      • 默认方法(Default Methods)
      • @FunctionalInterface注解
    • SPI
      • 理解SPI
    • 字节码
      • javaagent
      • 字节码操纵
      • 如何查看类编译后的字节码指令
      • 字节码指令有哪些
  • Python
    • 异常处理
  • Go
  • 数据结构与算法
    • 数据结构
      • 概述
        • 线性表
        • 栈
        • 队列
        • 串
        • 树
        • 图
      • Java的一些实现
      • 红黑树
      • 双缓冲队列
      • 跳表SkipList
    • 算法
      • 概述
      • 常见算法
        • 基本排序
        • 高级排序
        • 动态规划
  • 框架或工具
    • Spring
      • Spring基础
        • Spring整体架构
        • 什么是IoC
        • Ioc容器的基本实现
        • Spring的MainClass
          • Spring的BeanFactory
          • Spring的Register
          • Spring的Resource和ResourceLoader
          • Spring的PropertySource
          • Spring的PropertyResolver
          • Spring的PropertyEditor
          • Spring的Convert
          • Spring的BeanDefinition
          • Spring的BeanDefinitionReader
          • Spring的BeanDefiniton其他Reader
          • Spring的BeanDefinition其他Reader2
          • Spring的Aware
          • Spring的BeanFctoryPostProcessor
          • Spring的BeanPostProcessor
          • Spring的Listener
        • Xml格式的应用启动
          • Xml格式的应用启动2
          • Xml格式的应用启动3
          • Xml格式的应用启动4
          • Xml格式的应用启动5
          • Xml格式的应用启动6
          • Xml格式的应用启动7
        • Spring中的设计模式
        • 什么是AOP
        • Spring中AOP的实现
      • Spring应用
        • Spring的事务控制
        • @Transactional注解在什么情况下会失效
        • 如何在数据库事务提交成功后进行异步操作
        • Spring中定时任务的原理
    • SpringMVC
      • Controller是如何将参数和前端传来的数据一一对应的
      • 请求处理流程
    • Zookeeper
      • Zookeeper是什么
      • Zookeeper能干啥
    • Shiro
    • druid
    • Netty
    • Consul
      • Consul是什么
    • etcd
    • confd
    • Akka
      • Actor模型是什么
  • 数据库
    • 基本概念
    • MySQL
      • 基本配置
      • MySQL数据类型
      • MySQL存储引擎
      • MySQL事务
        • MySQL事务概念
      • MySQL索引
        • MySQL中的索引类型
        • B-Tree/B+Tree概述
        • 为什么使用B+Tree
        • MySQL中的B+Tree索引
        • MySQL高性能索引策略
      • MySQL查询
        • MySQL查询过程
        • MySQL查询性能优化
        • 使用EXPLAIN
      • MySQL锁
        • MySQL中锁概述
        • InnoDB的并发控制
        • MySQL乐观锁
      • MySQL分库分表
        • 分库/分表
        • 跨库JOIN
        • 跨库分页
        • 分库分表后的平滑扩容
        • 分区表
        • 分布式ID生成方法
      • MySQL实战
        • 在线表结构变更
        • MySQL优化规则
        • MySQL问题排查
        • 常见查询场景
    • Redis
    • Hbase
    • OpenTSDB
    • rrd
    • MongoDB
    • 连接池
  • 系统设计
    • 一致性Hash算法
    • 限流
      • 限流是什么
      • 限流算法
      • 应用内限流
      • 分布式限流
      • 接入层限流
        • ngx_http_limit_conn_module
        • ngx_http_limit_req_module
        • lua_resty_limit-tarffic
      • 节流
    • 降级
      • 降级详解
      • 人工降级开关的实现
      • 自动降级的实现:Hystrix
    • 负载均衡
      • 概述
      • 互联网架构下的负载均衡
      • Nginx负载均衡(七层)
      • Nginx负载均衡(四层)
      • Nginx动态配置
    • 超时与重试机制
      • 什么地方要超时与重试
      • 代理层超时与重试
      • Web容器超时
      • 中间件客户端超时与重试
      • 数据库超时
      • NoSQL客户端超时设置
      • 业务超时
      • 前端请求超时
    • 网关
    • CAP
      • 什么是CAP
      • CAP理解
    • 生产者-消费者模型
      • 使用notify/wait方式
      • 使用await/signal实现
      • 使用阻塞队列实现
      • 使用信号量实现
      • 使用管道流实现
      • 无锁队列Disruptor
      • 双缓冲队列
    • 缓存
      • 缓存概述
      • 数据库缓存
      • 应用缓存
      • 前端缓存
      • 本地缓存
    • 秒杀
    • LRU
  • 版本控制
    • Git
      • Git常用命令
      • 场景命令
    • Svn
  • 计算机操作系统
    • Linux
      • Linux中重要概念
      • 常用命令
      • 查看日志
      • 权限管理
      • 登录或传输
      • 防火墙
      • 配置ssh免密
      • 进程
      • 防火墙
    • Mac
    • 计算机基础
      • 进制
      • Java中的位运算
      • 计算机存储系统结构
  • 网络
    • TCP三次握手和四次挥手
    • 网络术语
      • 网关、路由器、交换机、IP等
      • VLAN
      • LAN
  • 设计模式
    • 设计模式概述
    • 创建型
      • 单例模式
      • 工厂模式
      • 建造者模式
      • 原型模式
      • 享元模式
    • 行为型
      • 观察者模式
      • 策略模式
      • 模板模式
      • 责任链模式
      • 命令模式
      • 外观模式
      • 迭代器模式
      • 中介者模式
        • 中介模式续
      • 状态模式
        • 状态模式实例
        • 状态模式思考
      • 访问者模式
        • 访问者实例1
        • 访问者模式续
    • 结构型
      • 组合模式
        • 组合模式续
      • 装饰模式
        • 装饰模式续
      • 代理模式
      • 备忘录模式
      • 桥接模式
        • 桥接模式实例一
  • 构建工具
    • Maven
      • 常用命令
      • Maven生命周期
      • Maven中的变量和属性
      • 不同环境的如何配置不同的变量
      • 常用插件及配置
      • 其他问题
      • dependencies与dependencyManagement的区别
    • Gradle
  • 大数据
    • Hadoop
    • Storm
    • Spark
  • 服务器
    • Tomcat
      • server.xml配置详解
      • 线程池和连接数配置
      • Maven远程部署
      • 一些小技巧
      • Tomcat类加载机制分析
      • Tomcat的日志
      • Tomcat架构
        • 概述
        • Server 的启动流程
        • 请求处理流程
    • Nginx
      • 常用命令
      • 基本配置
      • Lua
    • Tengine
  • 中间件
    • 任务调度
      • 为什么需要任务调度
    • 消息队列
      • 为什么需要消息队列
      • 消息队列关键点
      • 消息中间件需要解决的问题
      • 不同消息队列产品对比
      • RocketMQ
        • 快速入门
        • 整体架构
        • 部署方式
          • Broker部署方案
        • 客户端使用
          • 客户端使用指南
          • 快速开始
          • 简单示例
          • 有序消息示例
          • 广播消息示例
          • 定时消息示例
          • 批量消息示例
          • 过滤消息示例
          • 日志输出配置示例
        • 关键点实现
          • 顺序消息的实现
        • 最佳实践
          • Broker的最佳实践
          • 生产者最佳实践
            • 生产者最佳实践续
          • 消费者最佳实践
            • 消费者最佳实践续
          • 名称服务最佳实践
          • JVM/kernel配置的最佳实践
          • 新特性 Filter Server
          • 其他事项
      • RabbitMQ
      • Kafka
    • 分布式事务
      • 什么是分布式事务
      • 解决方案
    • 服务治理
      • RPC概念
      • RPC最简实现
      • 为什么需要服务治理
      • Dubbo
        • Dubbo整体架构
      • Java RMI
    • 分布式锁
      • 如何设计分布式锁
        • 基于zookeeper
        • 基于Redis
    • 注册中心
      • 注册中心的职责
      • 不同注册中心的比较
    • 配置中心
      • 概述
      • 配置中心的实现与选型
  • Web开发
    • Http请求类型及区别
    • 常见的content-type
    • 如何处理跨域
    • Restful最佳实践
    • HTTP状态码
    • Http下载原理
  • 测试
    • 压测:apache bench
    • 压测:Jmeter
Powered by GitBook
On this page
  • 深拷贝和浅拷贝
  • 实现
  • 方式1、重写clone()方法
  • 方式2、通过序列化实现深拷贝
  • 参考
  1. Java
  2. 面向对象

深拷贝和浅拷贝

Previous接口和抽象类的区别NextInteger.parseInt()与Interger.valueof()

Last updated 6 years ago

深拷贝和浅拷贝

下面列表是Java中深拷贝和浅拷贝的区别

Shallow Copy

Deep Copy

Cloned Object and original object are not 100% disjoint.

Cloned Object and original object are 100% disjoint.

Any changes made to cloned object will be reflected in original object or vice versa.

Any changes made to cloned object will not be reflected in original object or vice versa.

Default version of clone method creates the shallow copy of an object.

To create the deep copy of an object, you have to override clone method.

Shallow copy is preferred if an object has only primitive fields.

Deep copy is preferred if an object has references to other objects as fields.

Shallow copy is fast and also less expensive.

Deep copy is slow and very expensive.

表格来源:

浅拷贝

深拷贝

原对象和克隆对象并不是100%无关联

原对象和克隆对象100%无关联

对克隆对象的任何改变都会反映在原对象中,反之亦然

克隆对象的改变不会反映在原对象中,反之亦然

默认的clone()方法创建的是浅拷贝

要实现深拷贝,必须重写clone()方法

如果一个对象中字段只有基本类型,推荐浅拷贝

如果一个对象中字段存在其他对象的引用类型,推荐深拷贝

浅拷贝速度快,代价小

深拷贝相对较慢,代价大

浅拷贝

如果属性是基本类型,拷贝的就是基本类型的值;

如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。

深拷贝

拷贝所有的属性,并拷贝属性指向的实际对象(而非仅仅拷贝引用地址)。

实现

方式1、重写clone()方法

要对某个对象进行浅拷贝,只需要让该类实现Cloneable接口,并重写clone()方法即可:

public class Product {

    private String name;

    //省略 construct/setter/getter
}

public class Order implements Cloneable {

    private String id;

    private Product product;

    //省略 construct/setter/getter

    @Override
    public Order clone() throws CloneNotSupportedException{
        return (Order) super.clone();
    }
}
//测试
public static void main(String[] args) throws CloneNotSupportedException{

    Product product = new Product("book");
    Order order = new Order("123", product);
    Order clone = order.clone();

    System.out.println(order);
    System.out.println(clone);

    System.out.println(order.getProduct());
    System.out.println(clone.getProduct());
}
//输出如下(省略包名)
Order@5ca881b5
Order@24d46ca6
Product@4517d9a3
Product@4517d9a3

可以看到,对Order进行clone()后,生成的对象与原对象内存不一样,但其中的Product却是同一个。

这就是浅拷贝,对基本类型拷贝只,对引用类型,仅拷贝引用地址,并不实际拷贝引用的对象。

在上面的例子中,如果想要实现深拷贝:让Product类也实现cloneable接口,并重写clone()方法,并改造Order的clone()方法:

public class Product implements Cloneable {

    private String name;

    //省略 construct/setter/getter

    @Override
    public Product clone() throws CloneNotSupportedException {
        return (Product) super.clone();
    }
}

public class Order implements Cloneable {

    private String id;

    private Product product;

    public Order(String id, Product product) {
        this.id = id;
        this.product = product;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public Product getProduct() {
        return product;
    }

    public void setProduct(Product product) {
        this.product = product;
    }

    @Override
    public Order clone() throws CloneNotSupportedException {
        Order clone = (Order)super.clone();
        clone.product = this.product.clone();
        return clone;
    }
}
//测试如上,输出如下(省略包名):
Order@5ca881b5
Order@24d46ca6
Product@4517d9a3
Product@372f7a8d

可以发现,如果要实现深拷贝,需要对每个涉及的类重写clone()方法,并对类中的每个引用类型进行clone(),如果涉及的类的层次很深,那么就需要写非常多的clone()方法。

注意将重写的clone()方法的访问限制符由默认的protected改为public(如果要在包外用到clone()的话)。

方式2、通过序列化实现深拷贝

序列化会将整个对象写入到一个持久化存储文件中,并且当需要的时候把它读取回来(反序列化), 这意味着当你需要把它读取回来时你需要整个对象的一个拷贝。这就是当你深拷贝一个对象时真正需要的东西。请注意,当你通过序列化进行深拷贝时,必须确保对象中所有类都是可序列化的。

还是用上面的例子:

public class Product implements Serializable{

    private String name;

    //省略 construct/setter/getter
}

public class Order implements Serializable {

    private String id;

    private Product product;

    //省略 construct/setter/getter
}

//测试
public static void main(String[] args) throws IOException, ClassNotFoundException {

    Product product = new Product("book");
    Order order = new Order("123", product);

    // 序列化
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(bos);
    oos.writeObject(order);
    oos.flush();
    //反序列化
    ByteArrayInputStream bin = new ByteArrayInputStream(bos.toByteArray());
    ObjectInputStream ois = new ObjectInputStream(bin);
    Order serialOrder = (Order) ois.readObject();

    System.out.println(order);
    System.out.println(serialOrder);

    System.out.println(order.getProduct());
    System.out.println(serialOrder.getProduct());
}
//输出如下(省略包名)
Order@1f32e575
Order@49097b5d
Product@27716f4
Product@6e2c634b

序列化实现起来比较简单,不用对原有的类进行大幅改造,但有以下两个问题:

  • 无法序列化transient变量

  • 性能较差:创建一个socket,序列化一个对象,通过socket传输,然后再反序列化,这个过程与调用已有对象的方法相比是很慢的。它比通过实现Clonable接口这种方式来进行深拷贝几乎多花100倍的时间(这个倍数待求证)。

参考

Difference Between Shallow Copy Vs Deep Copy In Java
细说 Java 的深拷贝和浅拷贝
Java 深拷贝和浅拷贝
Java如何实现深拷贝