java中关于深拷贝的几种方式总结

 更新时间:2022年08月18日 10:26:48   作者:凌兮~  
这篇文章主要介绍了java中关于深拷贝的几种方式总结,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

前言

在java里,当我们需要拷贝一个对象时,有两种类型的拷贝:浅拷贝与深拷贝。

  • 浅拷贝只是拷贝了源对象的地址,所以源对象的值发生变化时,拷贝对象的值也会发生变化。
  • 深拷贝则是拷贝了源对象的所有值,所以即使源对象的值发生变化时,拷贝对象的值也不会改变。

在这里插入图片描述

方式1:构造函数深拷贝

我们可以调用构造函数进行深拷贝,形参如果是基本类型和字符串则是直接赋值,如果是对象,则是重新new一个。

测试案例

package com.lyj.demo.pojo.cloneTest;
import lombok.Getter;
/**
 * @author 凌兮
 * @date 2021/4/15 14:28
 * 通过构造器进行深拷贝测试
 */
@Getter
public class UserConstruct {
    private String userName;
    private AddressConstruct address;
    public UserConstruct() {
    }
    public UserConstruct(String userName, AddressConstruct address) {
        this.userName = userName;
        this.address = address;
    }
    public static void main(String[] args) {
        AddressConstruct address = new AddressConstruct("小区1", "小区2");
        UserConstruct user = new UserConstruct("小李", address);
        // 调用构造函数进行深拷贝
        UserConstruct copyUser = new UserConstruct(user.getUserName(), new AddressConstruct(address.getAddress1(), address.getAddress2()));
        // 修改源对象的值
        user.getAddress().setAddress1("小区3");
        // false
        System.out.println(user == copyUser);
        // false
        System.out.println(user.getAddress().getAddress1() == copyUser.getAddress().getAddress1());
        // false
        System.out.println(user.getAddress().getAddress1().equals(copyUser.getAddress().getAddress1()));
        // true
        System.out.println(user.getAddress().getAddress2().equals(copyUser.getAddress().getAddress2()));
    }
}
package com.lyj.demo.pojo.cloneTest;
import lombok.Getter;
import lombok.Setter;
/**
 * @author 凌兮
 * @date 2021/4/15 14:28
 */
@Getter
@Setter
public class AddressConstruct {
    private String address1;
    private String address2;
    public AddressConstruct() {
    }
    public AddressConstruct(String address1, String address2) {
        this.address1 = address1;
        this.address2 = address2;
    }
}

方式2:重载Clone()方法深拷贝

Object父类有个clone()的拷贝方法,不过它是protected类型的 ,我们需要重写它并修改为public类型,除此之外,子类还需要实现Cloneable接口来告诉JVM这个类上市可以拷贝的。

测试案例

package com.lyj.demo.pojo.cloneTest;
import lombok.Getter;
import lombok.Setter;
/**
 * @author 凌兮
 * @date 2021/4/15 14:49
 *
 */
@Setter
@Getter
public class AddressClone implements Cloneable{
    private String address1;
    private String address2;
    public AddressClone() {
    }
    public AddressClone(String address1, String address2) {
        this.address1 = address1;
        this.address2 = address2;
    }
    @Override
    protected AddressClone clone() throws CloneNotSupportedException {
        return (AddressClone) super.clone();
    }
}
package com.lyj.demo.pojo.cloneTest;
import lombok.Getter;
import lombok.Setter;
/**
 * @author 凌兮
 * @date 2021/4/15 14:48
 * 通过实现Clone接口实现深拷贝
 */
@Setter
@Getter
public class UserClone implements Cloneable{
    private String userName;
    private AddressClone address;
    public UserClone() {
    }
    public UserClone(String userName, AddressClone address) {
        this.userName = userName;
        this.address = address;
    }
    /**
     * Object父类有个clone()的拷贝方法,不过它是protected类型的,
     * 我们需要重写它并修改为public类型。除此之外,
     * 子类还需要实现Cloneable接口来告诉JVM这个类是可以拷贝的。
     * @return
     * @throws CloneNotSupportedException
     */
    @Override
    protected UserClone clone() throws CloneNotSupportedException {
        // 需要注意的是,super.clone()其实是浅拷贝,
        // 所以在重写UserClone类的clone()方法时,address对象需要调用address.clone()重新赋值
        UserClone userClone = (UserClone) super.clone();
        userClone.setAddress(this.address.clone());
        return userClone;
    }
    public static void main(String[] args) throws CloneNotSupportedException {
        AddressClone address = new AddressClone("小区1", "小区2");
        UserClone user = new UserClone("小李", address);
        UserClone copyUser = user.clone();
        user.getAddress().setAddress1("小区3");
        // false
        System.out.println(user == copyUser);
        // false
        System.out.println(user.getAddress().getAddress1().equals(copyUser.getAddress().getAddress1()));
    }
}

需要注意的是,super.clone()其实是浅拷贝,所以在重写User类的clone()方法时,address对象需要调用address.clone()重新赋值。

方式3:Apache Commons Lang序列化方式深拷贝

Java提供了序列化的能力,我们可以先将源对象进行序列化,再反序列化生成拷贝对象。但是,使用序列化的前提是拷贝的类(包括其成员变量)需要实现Serializable接口。

Apache Commons Lang包对Java序列化进行了封装,我们可以直接使用它。

测试案例

package com.lyj.demo.pojo.cloneTest;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
/**
 * @author 凌兮
 * @date 2021/4/15 15:11
 */
@Getter
@Setter
public class AddressSerializable implements Serializable {
    private String address1;
    private String address2;
    public AddressSerializable() {
    }
    public AddressSerializable(String address1, String address2) {
        this.address1 = address1;
        this.address2 = address2;
    }
}
package com.lyj.demo.pojo.cloneTest;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang3.SerializationUtils;
import java.io.Serializable;
/**
 * @author 凌兮
 * @date 2021/4/15 15:10
 * 通过Apache Commons Lang 序列化方式深拷贝
 * Java提供了序列化的能力,我们可以先将源对象进行序列化,再反序列化生成拷贝对象。
 * 但是,使用序列化的前提是拷贝的类(包括其成员变量)需要实现Serializable接口。
 * Apache Commons Lang包对Java序列化进行了封装,我们可以直接使用它。
 */
@Getter
@Setter
public class UserSerializable implements Serializable {
    private String userName;
    private AddressSerializable address;
    public UserSerializable() {
    }
    public UserSerializable(String userName, AddressSerializable address) {
        this.userName = userName;
        this.address = address;
    }
    public static void main(String[] args) {
        AddressSerializable address = new AddressSerializable("小区1", "小区2");
        UserSerializable user = new UserSerializable("小李", address);
        UserSerializable copyUser = SerializationUtils.clone(user);
        user.getAddress().setAddress1("小区3");
        // false
        System.out.println(user == copyUser);
        // false
        System.out.println(user.getAddress().getAddress1().equals(copyUser.getAddress().getAddress1()));
    }
}

方式4:Gson序列化方式深拷贝

Gson可以将对象序列化成JSON,也可以将JSON反序列化成对象,所以我们可以用它进行深拷贝。

测试案例

package com.lyj.demo.pojo.cloneTest;
import lombok.Data;
/**
 * @author 凌兮
 * @date 2021/4/15 15:31
 */
@Data
public class AddressGson {
    private String address1;
    private String address2;
    public AddressGson() {
    }
    public AddressGson(String address1, String address2) {
        this.address1 = address1;
        this.address2 = address2;
    }
}
package com.lyj.demo.pojo.cloneTest;
import com.google.gson.Gson;
import lombok.Data;
/**
 * @author 凌兮
 * @date 2021/4/15 15:30
 * 使用Gson序列化方式进行深拷贝
 * Gson可以将对象序列化成JSON,也可以将JSON反序列化成对象,所以我们可以用它进行深拷贝
 */
@Data
public class UserGson {
    private String userName;
    private AddressGson address;
    public UserGson() {
    }
    public UserGson(String userName, AddressGson address) {
        this.userName = userName;
        this.address = address;
    }
    public static void main(String[] args) {
        AddressGson address = new AddressGson("小区1", "小区2");
        UserGson user = new UserGson("小李", address);
        // 使用Gson序列化进行深拷贝
        Gson gson = new Gson();
        UserGson copyUser = gson.fromJson(gson.toJson(user), UserGson.class);
        user.getAddress().setAddress1("小区3");
        // false
        System.out.println(user == copyUser);
        // false
        System.out.println(user.getAddress().getAddress1().equals(copyUser.getAddress().getAddress1()));
    }
}

方式5:Jackson序列化方式

Jackson与Gson相似,可以将对象序列化成JSON,明显不同的地方是拷贝的类(包括其成员变量)需要有默认的无参构造函数。

测试案例

package com.lyj.demo.pojo.cloneTest;
import lombok.Data;
/**
 * @author 凌兮
 * @date 2021/4/15 15:41
 */
@Data
public class AddressJackson {
    private String address1;
    private String address2;
    public AddressJackson() {
    }
    public AddressJackson(String address1, String address2) {
        this.address1 = address1;
        this.address2 = address2;
    }
}
package com.lyj.demo.pojo.cloneTest;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Data;
/**
 * @author 凌兮
 * @date 2021/4/15 15:40
 * 通过Jackson方式实现深拷贝
 * Jackson与Gson相似,可以将对象序列化成JSON,明显不同的地方是拷贝的类(包括其成员变量)需要有默认的无参构造函数。
 */
@Data
public class UserJackson {
    private String userName;
    private AddressJackson address;
    public UserJackson() {
    }
    public UserJackson(String userName, AddressJackson address) {
        this.userName = userName;
        this.address = address;
    }
    public static void main(String[] args) throws JsonProcessingException {
        AddressJackson address = new AddressJackson("小区1", "小区2");
        UserJackson user = new UserJackson("小李", address);
        // 使用Jackson序列化进行深拷贝
        ObjectMapper objectMapper = new ObjectMapper();
        UserJackson copyUser = objectMapper.readValue(objectMapper.writeValueAsString(user), UserJackson.class);
        user.getAddress().setAddress1("小区3");
        // false
        System.out.println(user == copyUser);
        // false
        System.out.println(user.getAddress().getAddress1().equals(copyUser.getAddress().getAddress1()));
    }
}

总结

深拷贝方法优点缺点
构造函数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接口中的代理设计模式代码时实践

    java接口中的代理设计模式代码时实践

    这篇文章主要介绍了java接口中的代理设计模式代码时实践,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-07-07
  • 关于springboot2.4跨域配置问题

    关于springboot2.4跨域配置问题

    这篇文章主要介绍了springboot2.4跨域配置的方法,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-07-07
  • Eclipse添加servlet模板过程代码详解

    Eclipse添加servlet模板过程代码详解

    这篇文章主要介绍了Eclipse添加servlet模板过程代码详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-07-07
  • Mybatis实现增删改查

    Mybatis实现增删改查

    这篇文章主要介绍了Mybatis实现增删改查,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的小伙伴可以参考一下
    2022-01-01
  • dubbo如何实现consumer从多个group中调用指定group的provider

    dubbo如何实现consumer从多个group中调用指定group的provider

    这篇文章主要介绍了dubbo如何实现consumer从多个group中调用指定group的provider问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-03-03
  • Java数组去重复的18种方法示例

    Java数组去重复的18种方法示例

    这篇文章主要为大家介绍了Java数组去重复的18种写法示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-08-08
  • 解决MyBatis Mapper的XML文件SQL语句无法自动提示问题(亲测有效)

    解决MyBatis Mapper的XML文件SQL语句无法自动提示问题(亲测有效)

    这篇文章主要给大家介绍了如何解决MyBatis Mapper的XML文件SQL语句无法自动提示的问题,文中有详细的原因分析,以及通过图文介绍的解决方案,需要的朋友可以参考下
    2023-10-10
  • Mybatis多数据源切换实现代码

    Mybatis多数据源切换实现代码

    这篇文章主要介绍了Mybatis多数据源切换实现代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-10-10
  • Java中IO的NIO通道解析

    Java中IO的NIO通道解析

    这篇文章主要介绍了Java中IO的NIO通道解析,NIO 提供了与传统 BIO 模型中的 Socket 和 ServerSocket 相对应的 SocketChannel 和 ServerSocketChannel 两种不同的套接字通道实现,需要的朋友可以参考下
    2024-01-01
  • 使用IDEA工具配置和运行vue项目及遇到的坑

    使用IDEA工具配置和运行vue项目及遇到的坑

    这篇文章主要介绍了使用IDEA工具配置和运行vue项目及遇到的坑,需要的朋友可以参考下
    2018-09-09

最新评论