java 通过Object的clone复制对象

简介: java 通过Object的clone复制对象


java 通过Object的clone复制对象


需求背景

对象的克隆是指创建一个新的对象,且新的对象的状态与原始对象的状态相同。当对克隆的新对象进行修改时,不会影响原始对象的状态。


常规实现

image.png

运行结果

image.png

这个时候发现如果改了p2的地址属性,p1的地址属性也改变了


原因分析

出现这种情况的原因Person p2 = p1;这里将p1赋值给p2实际是将p1的引用给p2,在堆内存中p1和p2指向的是同一个对象,怎样才能实现最初的需求呢?


需求实现

因为每个类直接或间接的父类都是Object,因此它们都含有clone()方法,clone()是object类的protected 方法,所以都不能在类外进行访问。

image.png

要想对一个对象进行复制,就需要对clone方法覆盖。

同时只有类的对象自己可以克隆自己,所以对象类必须实现Cloneable接口才可以使用obj.clone()方法,

首先,如果这个对象的类不实现接口{@code Cloneable},然后{@code CloneNotSupportedException}被抛出,典型的方式:

image.png

image.png


代码实现

image.png


运行结果

image.png

此时的结果p2更改的地址属性并没有影响到p1的地址属性,需求达到。

到此为止实现的对象clone方法属于浅复制(shallow copy),那么什么是深复制(deep copy)

深复制

 大家都知道,在java语言中,分为基本数据类型和引用数据类型,基本数据类型包括(byte、short、int、long、float、double、boolean、char),引用数据类型包括(class、interface、[  ]),浅复制和深复制的主要区别就在于是否支持引用类型的成员变量的复制。

浅复制,clone()内部类似于创建一个新的对象并把对象中相应的字段通过赋值给新的对象,而引用数据类型的内容本身并不是克隆的,因此这种复制就叫浅复制。

那么


代码重现

增加Study对象

image.png

改造Person对象

image.png


运行程序

image.png


运行结果

image.png


可以看到p2更改的基础属性地址信息不影响p1的,但是p2更改的Study对象属性却会影响p1中对应的Study属性,这就是浅复制存在的问题

问题处理

在这种情况下需要对Study对象也实现Cloneable接口,同时重写clone方法覆盖

image.png


同时Person对象中需要处理Study对象的复制

image.png

这就是深复制

再次运行copy3()查看运行结果

image.png


这里可以看到p2对study属性的更改不再影响p1中study的属性。目标达成。


测试代码

TestClone.java

package com.example.demo;
import com.example.modules.person.domain.Person;
import com.example.modules.person.domain.Study;
/**
 * @author: dongao
 * @create: 2020/6/6
 */
public class TestClone {
    //测试clone
    public static void main(String[] args) {
        //copy();
        //copy2();
        copy3();
    }
    private static void copy() {
        Person p1 = new Person();
        p1.setName("小A");
        p1.setAddr("北京");
        p1.setAge(18);
        p1.setBirthday("2020-05-20");
        p1.setCountry("中国");
        System.out.println("p1-first:"+p1.getAddr());
        Person p2 = p1;
        System.out.println("p2-first:"+p2.getAddr());
        p2.setAddr("上海");
        System.out.println("p1-second:"+p1.getAddr());
        System.out.println("p2-second:"+p2.getAddr());
    }
    private static void copy2() {
        Person p1 = new Person();
        p1.setName("小A");
        p1.setAddr("北京");
        p1.setAge(18);
        p1.setBirthday("2020-05-20");
        p1.setCountry("中国");
        System.out.println("p1-first:"+p1.getAddr());
        Person p2 = p1.clone();
        System.out.println("p2-first:"+p2.getAddr());
        p2.setAddr("上海");
        System.out.println("p1-second:"+p1.getAddr());
        System.out.println("p2-second:"+p2.getAddr());
    }
    private static void copy3() {
        Person p1 = new Person();
        p1.setName("小A");
        p1.setAddr("北京");
        p1.setAge(18);
        p1.setBirthday("2020-05-20");
        p1.setCountry("中国");
        Study s1 = new Study();
        s1.setStuName("语文");
        p1.setStudy(s1);
        System.out.println("p1-first:"+p1.getAddr()+"study:"+p1.getStudy().getStuName());
        Person p2 = p1.clone();
        System.out.println("p2-first:"+p2.getAddr()+"study:"+p2.getStudy().getStuName());
        p2.setAddr("上海");
        p2.getStudy().setStuName("数学");
        System.out.println("p1-second:"+p1.getAddr()+"study:"+p1.getStudy().getStuName());
        System.out.println("p2-second:"+p2.getAddr()+"study:"+p2.getStudy().getStuName());
    }
}


Person.java

package com.example.modules.person.domain;
public class Person implements Cloneable
{
  private String id;
  private String name;
  private Integer age;
  private String country;
  private String birthday;
  private String addr;
  private String data;
  public String getId() {
    return id;
  }
  public void setId(String id) {
    this.id = id;
  }
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
  public Integer getAge() {
    return age;
  }
  public void setAge(Integer age) {
    this.age = age;
  }
  public String getCountry() {
    return country;
  }
  public void setCountry(String country) {
    this.country = country;
  }
  public String getBirthday() {
    return birthday;
  }
  public void setBirthday(String birthday) {
    this.birthday = birthday;
  }
  public String getAddr() {
    return addr;
  }
  public void setAddr(String addr) {
    this.addr = addr;
  }
  public String getData() {
    return data;
  }
  public void setData(String data) {
    this.data = data;
  }
  private Study study;
  public Study getStudy() {
    return study;
  }
  public void setStudy(Study study) {
    this.study = study;
  }
  @Override
  public Person clone() {
    Person per = null;
    try {
      per = (Person) super.clone();
      per.study = study.clone();
    } catch (CloneNotSupportedException e) {
      e.printStackTrace();
    }
    return per;
  }
}


Study.java

package com.example.modules.person.domain;
/**
 * @author: dongao
 * @create: 2020/6/6
 */
public class Study implements Cloneable{
    private String stuName;
    public String getStuName() {
        return stuName;
    }
    public void setStuName(String stuName) {
        this.stuName = stuName;
    }
    @Override
    public Study clone(){
        Study study = null;
        try {
             study = (Study) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return study;
    }
}


流方式复制对象

到这里基本问题都解决了,但是还可能会遇到一个问题,就是当前对象中有很多引用对象,这样的话通过clone的方法处理起来会比较麻烦,这是可以采用另外一种方式,序列化的方式来实现对象的深复制。


改造Person.java

image.png


这里最好显式指定serialVersionUID的值防止反序列化的时候出问题。


image.png

  public Person pclone() {
    Person per = null;
    try {
      //对象的序列化流,作用:把对象转成字节数据的输出到文件中保存,对象的输出过程称为序列化,可实现对象的持久存储。
      ByteArrayOutputStream bout = new ByteArrayOutputStream();
      ObjectOutputStream out = new ObjectOutputStream(bout);
      out.writeObject(this);
      out.close();
      // read a clone of the object from the byte array
      ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
      ObjectInputStream in = new ObjectInputStream(bin);
      per = (Person) in.readObject();
      in.close();
    } catch (Exception e) {
      e.printStackTrace();
    }
    return per;
  }


运行效果

image.png

**注:**大家可以自行复制代码测试,欢迎指正,相互学习。


相关文章
|
11月前
|
设计模式 网络协议 数据可视化
Java 设计模式之状态模式:让对象的行为随状态优雅变化
状态模式通过封装对象的状态,使行为随状态变化而改变。以订单为例,将待支付、已支付等状态独立成类,消除冗长条件判断,提升代码可维护性与扩展性,适用于状态多、转换复杂的场景。
1136 157
|
12月前
|
存储 消息中间件 人工智能
【05】AI辅助编程完整的安卓二次商业实战-消息页面媒体对象(Media Object)布局实战调整-按钮样式调整实践-优雅草伊凡
【05】AI辅助编程完整的安卓二次商业实战-消息页面媒体对象(Media Object)布局实战调整-按钮样式调整实践-优雅草伊凡
323 11
【05】AI辅助编程完整的安卓二次商业实战-消息页面媒体对象(Media Object)布局实战调整-按钮样式调整实践-优雅草伊凡
|
缓存 安全 Java
Java反射机制:动态操作类与对象
Java反射机制是运行时动态操作类与对象的强大工具,支持获取类信息、动态创建实例、调用方法、访问字段等。它在框架开发、依赖注入、动态代理等方面有广泛应用,但也存在性能开销和安全风险。本文详解反射核心API、实战案例及性能优化策略,助你掌握Java动态编程精髓。
|
存储 人工智能 JavaScript
Java从作用域到对象高级应用​
本内容详细讲解了JavaScript中的作用域类型(函数作用域、块作用域、全局作用域)、作用域链、垃圾回收机制、闭包、变量提升、函数参数、数组方法、内置构造函数、对象高级知识、原型链、对象赋值、深浅拷贝、递归、异常处理及this指向等内容,全面覆盖JS核心概念与编程技巧。
155 0
|
存储 Java
Java对象的内存布局
在HotSpot虚拟机中,Java对象的内存布局分为三部分:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。对象头包含Mark Word、Class对象指针及数组长度;实例数据存储对象的实际字段内容;对齐填充用于确保对象大小为8字节的整数倍。
267 0
|
JavaScript 前端开发 开发者
讲述Vue框架中用于对象响应式变化的Object.defineProperty函数。
综上所述,Vue.js通过 `Object.defineProperty()`提供了强大的响应式能力,使得状态管理变得简洁高效。这种能力是Vue.js受到广大开发者青睐的重要原因之一。尽管Vue 3.x使用Proxy替代了该方法,但对于Vue 2.x及其之前版本,`Object.defineProperty()`是理解Vue.js内部工作机制不可或缺的一部分。
343 27
|
Java 数据库连接 API
Java 对象模型现代化实践 基于 Spring Boot 与 MyBatis Plus 的实现方案深度解析
本文介绍了基于Spring Boot与MyBatis-Plus的Java对象模型现代化实践方案。采用Spring Boot 3.1.2作为基础框架,结合MyBatis-Plus 3.5.3.1进行数据访问层实现,使用Lombok简化PO对象,MapStruct处理对象转换。文章详细讲解了数据库设计、PO对象实现、DAO层构建、业务逻辑封装以及DTO/VO转换等核心环节,提供了一个完整的现代化Java对象模型实现案例。通过分层设计和对象转换,实现了业务逻辑与数据访问的解耦,提高了代码的可维护性和扩展性。
560 1
|
前端开发 Java 数据库连接
java bo 对象详解_全面解析 java 中 PO,VO,DAO,BO,POJO 及 DTO 等几种对象类型
Java开发中常见的六大对象模型(PO、VO、DAO、BO、POJO、DTO)各有侧重,共同构建企业级应用架构。PO对应数据库表结构,VO专为前端展示设计,DAO封装数据访问逻辑,BO处理业务逻辑,POJO是简单的Java对象,DTO用于层间数据传输。它们在三层架构中协作:表现层使用VO,业务层通过BO调用DAO处理PO,DTO作为数据传输媒介。通过在线商城的用户管理模块示例,展示了各对象的具体应用。最佳实践包括保持分层清晰、使用工具类转换对象,并避免过度设计带来的类膨胀。理解这些对象模型的区别与联系。
1060 1
|
Java
深入JavaSE:详解Java对象的比较。
总的来说,Java对象的比较就像海洋生物的比较,有外在的,有内在的,有面对所有情况的,也有针对特殊情况的。理解并掌握这些比较方式,就能更好地驾驭Java的世界,游刃有余地操作Java对象。
741 12
|
编解码 JavaScript 前端开发
【Java进阶】详解JavaScript的BOM(浏览器对象模型)
总的来说,BOM提供了一种方式来与浏览器进行交互。通过BOM,你可以操作窗口、获取URL、操作历史、访问HTML文档、获取浏览器信息和屏幕信息等。虽然BOM并没有正式的标准,但大多数现代浏览器都实现了相似的功能,因此,你可以放心地在你的JavaScript代码中使用BOM。
441 23