java中三种拷贝方法举例总结

 更新时间:2024年09月12日 10:36:52   作者:小库抢板12  
在Java编程中,理解引用拷贝、浅拷贝和深拷贝对于对象复制和内存管理至关重要,这篇文章主要介绍了java中三种拷贝方法的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下

前言

在Java编程中,理解深拷贝(Deep Copy)、浅拷贝(Shallow Copy)和引用拷贝(Reference Copy)是非常重要的。这三种拷贝方式涉及对象复制和内存管理。以下是对它们的详细解释:

1. 引用拷贝(Reference Copy)

引用拷贝是最简单的一种拷贝方式。它只复制对象的引用,而不复制对象本身。换句话说,原始对象和新对象共享同一个内存地址。

Person person1 = new Person("Alice");
Person person2 = person1;  // 引用拷贝

person2.setName("Bob");

System.out.println(person1.getName()); // 输出 "Bob"
System.out.println(person2.getName()); // 输出 "Bob"

在上述代码中,person1person2指向同一个对象,所以改变其中一个对象的属性,另一个对象的属性也会被改变。

2. 浅拷贝(Shallow Copy)

浅拷贝会创建一个新对象,但新对象中的成员变量(如果是对象)仍然是原对象的引用。浅拷贝仅复制对象的第一层属性。

可以通过实现Cloneable接口并重写clone方法来实现浅拷贝:

class Address {
    String city;

    public Address(String city) {
        this.city = city;
    }
}

class Person implements Cloneable {
    String name;
    Address address;

    public Person(String name, Address address) {
        this.name = name;
        this.address = address;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();  // 浅拷贝
    }
}

public class Main {
    public static void main(String[] args) throws CloneNotSupportedException {
        Address address = new Address("New York");
        Person person1 = new Person("Alice", address);
        Person person2 = (Person) person1.clone();

        person2.name = "Bob";
        person2.address.city = "Los Angeles";

        System.out.println(person1.name); // 输出 "Alice"
        System.out.println(person1.address.city); // 输出 "Los Angeles"
        System.out.println(person2.name); // 输出 "Bob"
        System.out.println(person2.address.city); // 输出 "Los Angeles"
    }
}

在上述代码中,person1person2拥有不同的name属性,但是共享同一个Address对象。

3. 深拷贝(Deep Copy)

深拷贝不仅创建一个新对象,还会递归地复制所有成员对象。这样,原对象和新对象完全独立,不共享任何引用。

深拷贝可以通过手动实现clone方法来完成,或者使用序列化。

手动实现深拷贝的示例:

class Address implements Cloneable {
    String city;

    public Address(String city) {
        this.city = city;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

class Person implements Cloneable {
    String name;
    Address address;

    public Person(String name, Address address) {
        this.name = name;
        this.address = address;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Person cloned = (Person) super.clone();
        cloned.address = (Address) address.clone();  // 深拷贝
        return cloned;
    }
}

public class Main {
    public static void main(String[] args) throws CloneNotSupportedException {
        Address address = new Address("New York");
        Person person1 = new Person("Alice", address);
        Person person2 = (Person) person1.clone();

        person2.name = "Bob";
        person2.address.city = "Los Angeles";

        System.out.println(person1.name); // 输出 "Alice"
        System.out.println(person1.address.city); // 输出 "New York"
        System.out.println(person2.name); // 输出 "Bob"
        System.out.println(person2.address.city); // 输出 "Los Angeles"
    }
}

在上述代码中,person1person2的所有属性都是独立的,修改一个对象的属性不会影响另一个对象。
通过序列化和反序列化来实现Java的深拷贝是一种通用且方便的方法。它可以确保对象的完整复制,包括所有嵌套的成员对象。以下是具体的实现步骤:

  • 让类实现Serializable接口:确保需要深拷贝的类和它包含的所有成员类都实现Serializable接口。
  • 使用序列化和反序列化进行深拷贝:将对象写入字节流,然后从字节流中读出对象,从而实现对象的完全复制。

下面是一个具体的例子:

示例代码

import java.io.*;

class Address implements Serializable {
    private static final long serialVersionUID = 1L;
    String city;

    public Address(String city) {
        this.city = city;
    }

    @Override
    public String toString() {
        return "Address{city='" + city + "'}";
    }
}

class Person implements Serializable {
    private static final long serialVersionUID = 1L;
    String name;
    Address address;

    public Person(String name, Address address) {
        this.name = name;
        this.address = address;
    }

    @Override
    public String toString() {
        return "Person{name='" + name + "', address=" + address + "}";
    }

    // 深拷贝方法
    public Person deepCopy() {
        try {
            // 序列化
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(this);
            oos.flush();
            oos.close();

            // 反序列化
            ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bis);
            return (Person) ois.readObject();
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
            return null;
        }
    }
}

public class Main {
    public static void main(String[] args) {
        Address address = new Address("New York");
        Person person1 = new Person("Alice", address);
        Person person2 = person1.deepCopy();

        person2.name = "Bob";
        person2.address.city = "Los Angeles";

        System.out.println("Original: " + person1);  // 原对象
        System.out.println("Copy: " + person2);  // 深拷贝后的对象
    }
}

代码解释

  • 实现Serializable接口:AddressPerson类都实现了Serializable接口,使它们可以被序列化。
  • 深拷贝方法deepCopy
    • 序列化:使用ObjectOutputStream将对象写入ByteArrayOutputStream
    • 反序列化:使用ObjectInputStreamByteArrayInputStream中读出对象,生成一个新的对象副本。
  • 测试深拷贝
    • 创建原始对象person1,并通过deepCopy方法生成person2
    • 修改person2的属性,验证原始对象person1不受影响,证明了对象的深拷贝。

这种方法的优点是实现简单,且适用于所有需要深拷贝的情况。然而,它也有一些限制,比如性能较慢(因为涉及IO操作)和必须实现Serializable接口。

附:深拷贝需要注意的点

在Java中,对象的深拷贝需要注意以下几点:

  • 不可拷贝对象的状态,只能拷贝对象的状态描述。Java中对象的状态包括实例变量和内部类实例等。
  • 对于可变对象,深拷贝后修改原始对象中的值也会修改拷贝对象中的值。因此,在进行深拷贝时需要创建新的对象,而不是直接引用原始对象。
  • 对于不可变对象,深拷贝后修改原始对象中的值不会影响拷贝对象中的值。
  • 对于数组和集合类对象,深拷贝时需要拷贝数组或集合中的每一个元素,而不仅仅是引用。
  • 对于非内存中的对象,例如文件、网络资源等,深拷贝时需要重新创建资源而不是简单地引用。
  • 对于非序列化的对象,深拷贝时需要通过实现Cloneable接口并重写clone()方法来实现。
  • 对于实现了Serializable接口的对象,可以通过序列化和反序列化来实现深拷贝。但是需要注意,如果对象中有不可序列化的字段,则无法通过序列化实现深拷贝。

总结

  • 引用拷贝:只复制引用,原对象和新对象指向同一个对象。
  • 浅拷贝:创建新对象,但不递归复制成员对象,成员对象仍然是共享的引用。
  • 深拷贝:创建新对象,并递归复制所有成员对象,完全独立。

这些拷贝方式在实际应用中有不同的使用场景和适用性,根据需要选择合适的拷贝方式可以有效管理内存和对象关系。

到此这篇关于java中三种拷贝方法的文章就介绍到这了,更多相关java拷贝方法内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 详解SpringBoot程序启动时执行初始化代码

    详解SpringBoot程序启动时执行初始化代码

    这篇文章主要介绍了详解SpringBoot程序启动时执行初始化代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-09-09
  • List集合对象中按照不同属性大小排序的实例

    List集合对象中按照不同属性大小排序的实例

    下面小编就为大家带来一篇List集合对象中按照不同属性大小排序的实例。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-03-03
  • Spring Boot使用模板引擎JSP实例解析

    Spring Boot使用模板引擎JSP实例解析

    这篇文章主要介绍了Spring Boot使用模板引擎JSP实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-10-10
  • SpringBoot整合RabbitMQ的5种模式实战

    SpringBoot整合RabbitMQ的5种模式实战

    本文主要介绍了SpringBoot整合RabbitMQ的5种模式实战,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-08-08
  • Mybatis-Plus insertBatch执行缓慢的原因查询

    Mybatis-Plus insertBatch执行缓慢的原因查询

    这篇文章主要介绍了Mybatis-Plus insertBatch执行缓慢的原因查询,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-11-11
  • Java动态代理详解及实例

    Java动态代理详解及实例

    这篇文章主要介绍了Java动态代理详解及实例的相关资料,需要的朋友可以参考下
    2017-01-01
  • 为什么SpringMVC中请求的body不支持多次读取

    为什么SpringMVC中请求的body不支持多次读取

    这篇文章主要介绍了为什么SpringMVC中请求的body不支持多次读取,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-12-12
  • 解决springboot利用ConfigurationProperties注解配置数据源无法读取配置信息问题

    解决springboot利用ConfigurationProperties注解配置数据源无法读取配置信息问题

    今天在学习springboot利用ConfigurationProperties注解配置数据源的使用遇到一个问题无法读取配置信息,发现全部为null,纠结是哪里出了问题呢,今天一番思考,问题根源找到,下面把我的解决方案分享到脚本之家平台,感兴趣的朋友一起看看吧
    2021-05-05
  • idea maven项目启动项目不编译target 文件的问题及解决方法

    idea maven项目启动项目不编译target 文件的问题及解决方法

    代码编辑器中无编译错误,通过maven 的clean 、compile、package进行各种操作也都没问题,但是单击绿色箭头运行(默认会先执行IDE本身的Build操作)却报:程序包xxx不存在,这篇文章主要介绍了解决idea maven项目启动项目不编译target文件问题,需要的朋友可以参考下
    2023-05-05
  • SpringBoot实现设置动态定时任务的方法详解

    SpringBoot实现设置动态定时任务的方法详解

    这篇文章主要介绍了SpringBoot实现设置动态定时任务的方法详解,SpringBoot是一个快速开发的Java框架,而动态定时任务是指可以在运行时动态添加、修改和删除定时任务的功能,需要的朋友可以参考下
    2023-10-10

最新评论