Java中对象如何拷贝?

hi,我是程序员王也,一个资深Java开发工程师,平时十分热衷于技术副业变现和各种搞钱项目的程序员~,如果你也是,可以一起交流交流。

今天我们来聊一聊Java中的对象拷贝~
在这里插入图片描述


浅拷贝与深拷贝

在Java中,对象拷贝可以是浅拷贝(Shallow Copy)或深拷贝(Deep Copy)。理解这两种拷贝的区别对于正确地实现对象拷贝至关重要。

  1. 介绍浅拷贝和深拷贝的基本概念

    • 浅拷贝:创建一个新对象,所有非静态字段的值都直接从原对象复制到新对象。如果字段是基本数据类型,则复制其值;如果是引用类型,则复制对对象的引用。

    • 深拷贝:创建一个新对象,所有非静态字段的值都被递归复制。这意味着不仅复制原对象的值,还复制引用对象的值,从而创建一个完全独立的副本。

  2. 解释浅拷贝与深拷贝的区别及其对对象引用的影响

    浅拷贝和深拷贝的主要区别在于对引用类型字段的处理。在浅拷贝中,引用类型字段的引用被复制,因此原始对象和拷贝对象共享相同的引用类型字段。在深拷贝中,引用类型字段被递归复制,因此原始对象和拷贝对象的引用类型字段是独立的。

案例源码说明

以下是一个展示浅拷贝和深拷贝区别的示例:

public class CopyDemo {
    static class Person {
        private String name;
        private Address address; // 引用类型

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

        // Getters and setters are omitted for brevity
    }

    static class Address {
        private String street;
        private String city;

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

        // Getters and setters are omitted for brevity
    }

    public static void main(String[] args) {
        Person original = new Person("John", new Address("123 Main St", "Anytown"));
        Person copy = shallowCopy(original); // 假设这是浅拷贝方法

        // 修改拷贝对象的地址信息
        copy.getAddress().setStreet("456 Elm St");

        // 打印原始对象和拷贝对象的地址信息
        System.out.println("Original Address: " + original.getAddress().getStreet());
        System.out.println("Copy Address: " + copy.getAddress().getStreet());
    }

    // 浅拷贝方法示例
    public static Person shallowCopy(Person original) {
        return new Person(original.getName(), original.getAddress());
    }
}

在这个例子中,Person类有一个Address类型的引用字段。shallowCopy方法实现了一个浅拷贝,它复制了Person对象的name字段和address字段的引用。因此,当拷贝对象的地址信息被修改时,原始对象的地址信息也发生了变化。

如果需要深拷贝,我们需要为Address类也实现一个拷贝构造器或复制方法,以确保address字段也是独立的副本。


实现对象拷贝的几种方式

在Java中,实现对象拷贝可以通过多种方式,每种方式都有其适用场景和限制。以下是几种常用的对象拷贝实现方式。

  1. 使用Object.clone()方法实现浅拷贝

    所有继承自java.lang.Object的类都继承了clone()方法。通过重写clone()方法并调用super.clone(),可以实现对象的浅拷贝。

    public class CloneableExample implements Cloneable {
        private String name;
        private Address address;
    
        public CloneableExample(String name, Address address) {
            this.name = name;
            this.address = address;
        }
    
        protected Object clone() throws CloneNotSupportedException {
            return super.clone(); // 浅拷贝
        }
    
        // Getters and setters are omitted for brevity
    }
    

    在这个例子中,CloneableExample类实现了Cloneable接口,并重写了clone()方法。调用clone()方法将创建一个新对象,但address字段仍然是共享的,因此这是一个浅拷贝。

  2. 利用复制构造器实现对象拷贝

    复制构造器是一种特殊的构造器,它接受一个同类型对象的引用,并初始化新对象以反映原对象的状态。

    public class CopyConstructorExample(CloneableExample orig) {
        private String name;
        private Address address;
    
        public CopyConstructorExample(CloneableExample orig) {
            this.name = orig.name; // 复制基本数据类型字段
            this.address = orig.address != null ? new Address(orig.address) : null; // 深拷贝引用类型字段
        }
    
        // Getters and setters are omitted for brevity
    }
    

    在这个例子中,复制构造器通过复制基本数据类型字段和创建引用类型字段的新实例来实现深拷贝。

  3. 使用copy方法手动实现对象拷贝

    自定义一个copy方法来手动拷贝对象的每个字段。

    public class ManualCopyExample {
        private String name;
        private Address address;
    
        public ManualCopyExample copy() {
            ManualCopyExample copy = new ManualCopyExample();
            copy.name = this.name; // 复制基本数据类型字段
            copy.address = this.address != null ? new Address(this.address) : null; // 深拷贝引用类型字段
            return copy;
        }
    
        // Getters and setters are omitted for brevity
    }
    

    在这个例子中,copy方法创建了一个新的ManualCopyExample对象,并手动复制了每个字段的值,实现了深拷贝。

  4. 通过序列化机制实现深拷贝

    利用Java的序列化机制,可以实现对象的深拷贝。

    public class SerializationCopyExample implements Serializable {
        private String name;
        private Address address;
    
        public SerializationCopyExample deepCopy() throws IOException, ClassNotFoundException {
            ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
            ObjectOutputStream out = new ObjectOutputStream(byteOut);
            out.writeObject(this);
    
            ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());
            ObjectInputStream in = new ObjectInputStream(byteIn);
            return (SerializationCopyExample) in.readObject();
        }
    
        // Getters and setters are omitted for brevity
    }
    

    在这个例子中,deepCopy()方法通过序列化当前对象到一个字节流,然后从该字节流中反序列化来创建一个深拷贝。


拷贝中的问题及解决方案

在实现对象拷贝时,可能会遇到一些常见问题,如处理循环引用、维护对象状态的一致性、以及确保拷贝的正确性。以下是一些可能遇到的问题及其解决方案。

  1. 处理循环引用

    当对象图中存在循环引用时,拷贝可能会导致无限递归。解决这个问题的一种方法是使用一个已经拷贝的对象的映射来检查和避免重复拷贝。

    public class DeepCopyWithCycleDetection {
        private Map<Object, Object> copiedObjects = new HashMap<>();
    
        public <T extends DeepCopyWithCycleDetection> T deepCopy() {
            if (!copiedObjects.containsKey(this)) {
                T copy = this.getClass().cast(InstantiationUtil.newInstance(this.getClass()));
                copiedObjects.put(this, copy);
    
                // 复制字段
                for (Field field : this.getClass().getDeclaredFields()) {
                    field.setAccessible(true);
                    try {
                        Object value = field.get(this);
                        if (value instanceof DeepCopyWithCycleDetection) {
                            @SuppressWarnings("unchecked")
                            T deepCopedValue = (T) ((DeepCopyWithCycleDetection) value).deepCopy();
                            field.set(copy, deepCopedValue);
                        } else {
                            field.set(copy, value);
                        }
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
            return (T) copiedObjects.get(this);
        }
    }
    

    在这个例子中,DeepCopyWithCycleDetection类使用一个Map来跟踪已经拷贝的对象,避免了循环引用的问题。

  2. 维护对象状态的一致性

    在某些情况下,对象的拷贝需要维护与原对象的状态一致性。这可以通过实现Cloneable接口并重写clone()方法来实现。

    @Override
    protected Object clone() throws CloneNotSupportedException {
        try {
            return super.clone(); // 调用Object的clone方法进行浅拷贝
        } catch (CloneNotSupportedException e) {
            throw new AssertionError(); // 永不发生
        }
    }
    

    如果需要深拷贝,你需要在clone()方法中手动拷贝每个引用类型字段。

  3. 确保拷贝的正确性

    拷贝操作应该确保拷贝后的对象与原对象在逻辑上是等价的。这可能需要在拷贝过程中执行一些验证逻辑。

    public class ValidatingCopyExample {
        private String data;
    
        public ValidatingCopyExample(String data) {
            this.data = data;
        }
    
        public ValidatingCopyExample validateAndCopy() {
            // 验证数据
            if (data == null || data.isEmpty()) {
                throw new IllegalStateException("Invalid data for copy");
            }
            // 创建拷贝
            return new ValidatingCopyExample(new String(data));
        }
    }
    

    在这个例子中,validateAndCopy()方法在拷贝之前验证了数据的有效性。


对象拷贝的最佳实践

在Java中进行对象拷贝时,遵循最佳实践是非常重要的,这不仅可以提高代码的可读性和可维护性,还可以避免潜在的错误。以下是一些对象拷贝的最佳实践。

  1. 明确拷贝的类型

    在设计类时,应该明确对象拷贝是浅拷贝还是深拷贝,并在文档中清晰地说明这一点。

  2. 使用复制构造器或拷贝方法

    提供一个复制构造器或拷贝方法来创建对象的副本,这可以使得拷贝过程更加可控和易于管理。

    public class Person {
        private String name;
        private Address address;
    
        public Person(Person original) {
            this.name = new String(original.name); // 深拷贝字符串
            this.address = original.address != null ? new Address(original.address) : null; // 深拷贝Address对象
        }
    
        // Getters and setters are omitted for brevity
    }
    
  3. 处理对象的不可变性

    如果类是不可变的(即一旦创建就不能被修改),那么拷贝对象将非常简单,因为不需要担心原始对象的状态被改变。

    public final class ImmutablePerson {
        private final String name;
        private final Address address;
    
        public ImmutablePerson(String name, Address address) {
            this.name = name;
            this.address = address;
        }
    
        // No setters, only getters
    }
    
  4. 使用序列化进行深拷贝

    当对象的拷贝逻辑非常复杂时,可以考虑使用序列化机制来实现深拷贝。

    public class SerializablePerson implements Serializable {
        private String name;
        private transient Address address; // 标记为transient以避免直接序列化
    
        public SerializablePerson deepCopy() throws IOException, ClassNotFoundException {
            ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
            ObjectOutputStream out = new ObjectOutputStream(byteOut);
            out.writeObject(this);
    
            ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());
            ObjectInputStream in = new ObjectInputStream(byteIn);
            return (SerializablePerson) in.readObject();
        }
    
        // Custom serialization logic to handle non-serializable fields like Address
    }
    
  5. 避免在拷贝构造器中执行复杂操作

    拷贝构造器或拷贝方法应该只负责拷贝数据,避免执行可能会影响性能的复杂操作。

  6. 考虑拷贝的效率

    在需要频繁拷贝对象的场景下,考虑拷贝操作的效率,避免不必要的深拷贝,除非确实需要。

  7. 确保拷贝对象的安全性

    在拷贝对象时,确保不会泄露敏感信息或违反安全性原则。

案例源码说明

以下是一个使用复制构造器实现对象拷贝的示例:

public class Address {
    private String street;
    private String city;

    public Address(Address original) {
        this.street = original.street; // 假设Address是不可变的
        this.city = original.city;
    }

    // Getters and possibly setters
}

public class Person {
    private String name;
    private Address address;

    public Person(Person original) {
        this.name = new String(original.name); // 深拷贝字符串
        this.address = new Address(original.address); // 使用复制构造器进行深拷贝
    }

    // Getters and setters
}

public class DeepCopyExample {
    public static void main(String[] args) {
        Person original = new Person("John", new Address("Main St", "Anytown"));
        Person copy = new Person(original);

        // 修改拷贝对象的地址信息,原始对象不受影响
        copy.getAddress().setStreet("Elm St");
    }
}

在这个例子中,我们使用了复制构造器来创建PersonAddress对象的深拷贝。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/561359.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

向量数据库的崛起:如何改变数据存储与机器学习的未来

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

便携的图像背景去除工具PhotoScissors9.2版本在Windows系统的下载与安装配置

目录 前言一、PhotoScissors安装二、使用配置总结 前言 ​“ PhotoScissors是一个多功能和强大的照片编辑工具&#xff0c;专门为Windows用户寻求一个直观的解决方案&#xff0c;背景删除和图像编辑。作为专用的Windows软件&#xff0c;PhotoScissors提供了一个用户友好的平台…

vlan 和 trunk实验

VLAN&#xff08;Virtual Local Area Network&#xff09;&#xff0c;即虚拟局域网&#xff0c;是一种网络技术&#xff0c;它的主要原理是将物理网络划分为多个逻辑子网&#xff0c;每个子网形成一个独立的广播域。这样&#xff0c;VLAN内的主机间通信就像在同一个局域网内一…

[Java基础揉碎]集合

目录 集合的理解和好处 数组 集合的理解和好处 继承图 ​编辑 简单实例 Collection接口和常用方法 1) add:添加单个元素 2) remove:删除指定元素 3) contains:查找元素是否存在 4) size:获取元素个数 5) isEmpty:判断是否为空 ​编辑 6) clear:清空 7) addAll:添…

碎碎笔记01

凹凸性 一元函数 凸函数&#xff1a;二阶导数>0 f ( x ) x 2 f(x) x^2 f(x)x2的二阶导数是 2&#xff0c;>0凹函数&#xff1a;二阶导数<0 驻点&#xff0c;拐点 驻点&#xff1a;增减性的交替点 拐点&#xff1a;凹凸性的交替点 脑补 f ( x ) s i n x f(x) …

揭阳硕榕超市管理系统的设计与实现(论文)_kaic

摘 要 在互联网高速发展环境下&#xff0c;传统的管理手段无法满足对信息的高效、快速的管理要求。为顺应时代发展的需要&#xff0c;提高超市的管理效能&#xff0c;提高超市的管理速度&#xff0c;构建一个信息化的工作流程&#xff0c;揭阳硕榕超市管理系统应运而生。 根…

spring boot: 使用MyBatis从hive中读取数据

一、hive表&#xff1a; 启动hiveserver2 二、添加mybatis starter和hive依赖 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instan…

[阅读笔记23][JAM]JOINTLY TRAINING LARGE AUTOREGRESSIVE MULTIMODAL MODELS

这篇论文是24年1月发表的&#xff0c;然后是基于的RA-CM3和CM3Leon这两篇论文。它所提出的JAM结构系统地融合了现有的文本模型和图像生成模型。 主要有两点贡献&#xff0c;第一点是提出了融合两个模型的方法&#xff0c;第二点是为混合模型精心设计的指令微调策略。 下图是一个…

【Arduino IDE 环境配置】

目录 Arduino IDE 环境配置 1. 安装方式2. 操作方法&#xff08;Arduino中文社区&#xff09; 2.1. 安装Arduino IDE2.2. 下载固件2.3. 修改Arduino IDE语言2.4. 添加开发板管理网址2.5. 运行离线包2.6. 检查安装是否成功 下载Arduino IDE&#xff1a; 如果你还没有安装Arduin…

Spring Boot + Thymeleaf 实现的任务发布网站

角色&#xff1a; 管理员雇主雇员 功能 雇主&#xff1a;登录、注册、发布任务、选择中标雇员、评价雇员雇员&#xff1a;登录、注册、查看任务列表、投标任务、收藏任务、完成任务管理员、登录、任务管理、雇主管理、雇员管理 部分功能截图 部署 导入数据库…

MySQL学习-非事务相关的六大日志、InnoDB的三大特性以及主从复制架构

一. 六大日志 慢查询日志:记录所有执行时间超过long_query_time的查询&#xff0c;方便定位并优化。 # 查询当前慢查询日志状态 SHOW VARIABLES LIKE slow_query_log; #启用慢查询日志 SET GLOBAL slow_query_log ON; #设置慢查询文件位置 SET GLOBAL slow_query_log_file …

Unity实现动态数字变化

最近的项目需要动态显示数字&#xff0c;所以使用Text组件&#xff0c;将数字进行变化操作过程记录下来。 一、UI准备 1、新建一个Text组件 2、新建C#脚本 3、将Text挂载到脚本上 二、函数说明 1、NumberChange 方法 NumberChange 方法接收四个参数&#xff1a;初始数字 in…

maven问题汇总

​ 1、报错 failed to transfer from http://0.0.0.0/ during a previous attempt. com.byd.xxx:xxx-parent:pom:1.1.0-SNAPSHOT failed to transfer from http://0.0.0.0/ during a previous attempt. This failure was cached in the local repository and resolution is no…

一分钟成为点灯大师(超简单5行代码-STM32F407的HAL实现按键轮询点亮LED灯)

一、开发环境 硬件&#xff1a;正点原子探索者 V3 STM32F407 开发板 单片机&#xff1a;STM32F407ZGT6 Keil版本&#xff1a;5.32 STM32CubeMX版本&#xff1a;6.9.2 STM32Cube MCU Packges版本&#xff1a;STM32F4 V1.27.1 使用STM32F407的HAL库实现按键轮询读取按键值&…

ssh-key关于authorized_keys电脑与linux互相认证

思路&#xff1a; 在A上生成公钥私钥。将公钥拷贝给server B&#xff0c;要重命名成authorized_keys(从英文名就知道含义了)Server A向Server B发送一个连接请求。Server B得到Server A的信息后&#xff0c;在authorized_key中查找&#xff0c;如果有相应的用户名和IP&#xf…

C语言Linux vim shell命令

1. actionmotion dG删到文件尾 ggdG先到开头再删除到末尾 d^到达行首 d$到行尾 2. num action 2dd删除两行 t"向后寻找"找到&#xff0c;找到前面一个位置 f"向后寻找"找到&#xff0c;直接找到本来的位置 diw删除单词并保持在视图状态&#xff…

抖音abogus(收部Pixel2手机退坑的dd我走咸鱼淘宝)

声明 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01;wx a15018601872 本文章未…

结构体输出字符串末尾有奇怪字符

今天打开之前的陈年老题&#xff0c;发现一个思路全对&#xff0c;但是答案错误的简单结构体的题&#xff0c;发现这个字符串输出末尾有奇怪字符&#xff0c;后来经过一番搜索发现&#xff0c;是因为给字符串定义的时候分配的空间不够&#xff0c;所以多分配一些就好啦 修改后&…

python-自动化篇-终极工具-用GUI自动控制键盘和鼠标-pyautogui-键盘

文章目录 键盘键盘——记忆宫殿入门——通过键盘发送一个字符串——typewrite()常规——键名——typewrite()常规——按下键盘——keyDown()常规——释放键盘——keyUp()升级——热键组合——hotkey() 键盘 pyautogui也有一些函数向计算机发送虚拟按键&#xff0c;让你能够填充…

OpenHarmony 网络管理-Socket连接

介绍 本示例主要演示了Socket在网络通信方面的应用&#xff0c;展示了Socket在两端设备的连接验证、聊天通信方面的应用。 效果预览 使用说明 1.搭建服务器环境&#xff1a;修改服务器脚本中的服务端IP地址&#xff0c;与本机IP地址保持一致&#xff0c;修改完成后双击运行脚…