绝不忽视!List.add方法揭秘:你绝对需要了解的覆盖现象

在这里插入图片描述

文章目录

  • 引言
  • 一、背景介绍
    • 1.1 事件背景
    • 1.2 List.add()方法简介
      • 示例
      • 影响
  • 二、覆盖现象
    • 解决方案
      • 1. 每次循环创建新对象
      • 2. 使用工厂方法或建造者模式
      • 3. 深拷贝
        • 4. 不可变对象
  • 三、解决方案
      • 1. 使用深拷贝
      • 2. 创建新对象
      • 3. 避免直接修改原对象
  • 四、 结论

引言

在 Java 编程中,使用 List 接口时,特别是其中的 add 方法,可能会遇到一种常见但易被忽视的问题——覆盖现象。这种现象可能导致意想不到的结果,影响程序的正确性和可维护性。

一、背景介绍

1.1 事件背景

在使用List进行对象添加的时候,发现对象都被覆盖了。刚开始以为是赋值出现的问题。后来打印发现值都很正常。

业务需求:在一份PDF文件中,有不定页数的详细信息。每页详细信息都是一样的,需要把每页的详细信息组装成为一行进行返回。
解决思路:根据x,y定位进行循环读取,然后把每个地位的结果当作一个对象列表进行返回。
主要代码如下:

        List<List<ItalyInfo>> list = new ArrayList<>();
        try (PDDocument document = Loader.loadPDF(inputFile)) {
            System.out.println("d:" + inputFile.getName());
            //循环获取详细
            for (int i = 0; i < document.getNumberOfPages(); i++) {
                // 创建一个新的 ItalyInfo 集合对象 
                List<ItalyInfo> italyList2 = getItalytInfoList(italyInfoList, document, i);
                italyList2.addAll(italyList);
             
                // 创建一个新的 ItalyInfo 集合对象
                List<ItalyInfo> newList = new ArrayList<>(italyList2);
                //打印返回的数据
                if(document.getNumberOfPages()>2) {
                    log.info("正常输出>>>"+JSON.toJSONString(newList));
                }
                list.add(newList);
            }
        }
        ...
//根据定位 将获取的内容输出
   private static List<ItalyInfo> getItalyInfoList(List<ItalyInfo> italyInfoListTmp, PDDocument document, int pageIndex) throws IOException {
        List<ItalyInfo> list = CollUtil.newArrayList();
        for (ItalyInfo italyInfo : italyInfoListTmp) { 
            Rectangle rectangle = new Rectangle((int) italyInfo.getPoint().getX(), y, italyInfo.getWidth(), italyInfo.getHeight());
            String regionName = "regionName" + italyInfo.getName() + "-" + pageIndex;
            textStripper2.addRegion(regionName, rectangle);

            PDPage page = document.getPage(pageIndex);
            textStripper2.extractRegions(page);
            String text = textStripper2.getTextForRegion(regionName); 
            italyInfo.setValue(text);
         	list.add(italyInfo);
         }
        return list;
    }     

期望的结果,是每行逐一进行返回,结果返回的值如下:

# 省略了无关的属性值
[
	[
		{
			"value": "2023"
		}
	],
	[
		{
			"value": "2023"
		}
	],
	[
		{
			"value": "2023"
		}
	]
]

所有的值都是一模一样的。
debug输出的值:

输出>>> [{"value": "2021"}]
输出>>> [{"value": "2022"}]
输出>>> [{"value": "2023"}]

所以基本上可以断定代码问题行在add的时候出现了问题。代码行如下:

  list.add(newList);

我们的对象在add的时候被“覆盖了”。
在解决覆盖这个问题之前,我们先深入的了解下List.add()方法。

1.2 List.add()方法简介

在Java中,List接口的add()方法是用于向列表中添加一个元素的方法。这个方法接受一个参数,即要添加的对象的引用,并将其添加到列表的末尾。由于Java中的List是基于引用传递的,所以add()方法添加的实际上是对象的引用,而不是对象的一个副本。

这意味着,如果你向List中添加了一个对象的引用,那么列表中的所有指向该对象的引用都会指向同一个实际对象。因此,如果你修改了列表中的某个对象,那么所有引用该对象的地方都会反映这个修改,因为它们实际上指向的是同一个内存地址。

示例

List<User> userList = new ArrayList<>();

User user1 = new User("Alice");
User user2 = new User("Bob");

userList.add(user1); // 添加user1的引用到列表
userList.add(user2); // 添加user2的引用到列表

在这个示例中,user1user2是两个不同的User对象。当我们调用userList.add(user1)时,我们实际上是将user1这个对象的引用添加到了userList中。同样地,当我们添加user2时,我们添加的是user2这个对象的引用。

影响

由于add()方法添加的是引用,所以如果你修改了列表中的某个对象的属性,那么所有引用该对象的地方都会看到这些更改。例如:

user1.setName("Charlie");
// 此时,userList中的第一个元素的姓名也会变为"Charlie",因为它们都指向同一个User对象。

在这里插入图片描述

二、覆盖现象

覆盖现象在编程中是一个需要注意的问题,特别是当处理对象引用和集合时。在Java中,List是一种引用类型的集合,这意味着当你向List中添加一个对象时,实际上是添加了一个对该对象的引用。如果你不小心将同一个对象的引用多次添加到List中,那么对这个对象的任何修改都会反映到List中的所有对应位置上,因为它们实际上都指向同一个对象。

解决方案

为了避免覆盖现象,可以采取以下几种策略:

1. 每次循环创建新对象

确保在循环的每次迭代中都创建一个新的对象实例,然后将该对象添加到List中。这样,即使修改了一个对象的属性,也不会影响到List中的其他对象,因为它们是完全独立的对象。

List<User> userList = new ArrayList<>();

for (int i = 0; i < 5; i++) {
    User user = new User(); // 每次循环都创建一个新的User对象
    user.setId(i);
    user.setName("User " + i);
    userList.add(user); // 添加新对象到列表
}

2. 使用工厂方法或建造者模式

如果对象的创建过程比较复杂,可以考虑使用工厂方法或建造者模式来创建不可变对象。这样可以保证每次创建的都是全新的、独立的实例,从而避免了覆盖现象。

UserFactory factory = new UserFactory();
List<User> userList = new ArrayList<>();

for (int i = 0; i < 5; i++) {
    userList.add(factory.createUser(i, "User " + i));
}

3. 深拷贝

如果对象内部包含其他对象,并且你需要复制整个对象结构,可以考虑实现深拷贝。深拷贝会创建对象及其所有引用对象的完整副本,从而避免了修改一个对象影响到其他对象的问题。

List<User> userList = new ArrayList<>();

for (int i = 0; i < 5; i++) {
    User originalUser = new User();
    originalUser.setId(i);
    originalUser.setName("User " + i);
    User copiedUser = deepCopy(originalUser); // 使用深拷贝创建新对象
    userList.add(copiedUser); // 添加新对象到列表
}
4. 不可变对象

设计不可变对象是避免覆盖现象的另一个有效方法。不可变对象的状态在创建后就不能被改变。这样,即使多个引用指向同一个对象,也不需要担心对象的状态会被修改。

public final class User {
    private final int id;
    private final String name;

    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }

    // Getters and other methods
}

List<User> userList = new ArrayList<>();

for (int i = 0; i < 5; i++) {
    userList.add(new User(i, "User " + i));
}

通过以上方法,可以有效地避免在操作List时出现覆盖现象,从而确保程序的正确性和稳定性。在实际开发中,应根据具体需求和上下文选择最合适的解决方案。在这里插入图片描述

三、解决方案

在处理Java中的List对象时,确实可能会遇到对象共享和覆盖的问题。为了避免这些问题,可以采取以下解决方案:

1. 使用深拷贝

深拷贝意味着创建一个对象的完整副本,包括对象内部所有引用的其他对象。这样可以确保修改副本中的任何内容都不会影响原始对象或其他副本。在Java中,你可以使用一些第三方库来实现深拷贝,例如Apache Commons Lang的SerializationUtils类或者Spring框架中的BeanUtils

import org.springframework.beans.BeanUtils;

public void deepCopyExample() {
    OriginalObject original = ...; // 原始对象
    CopyStrategy strategy = new CopyStrategy(); // 深拷贝策略
    DeepCopyable copier = new DeepCopyable() {
        @Override
        protected <T extends Copiable> T copy(T source, CopyStrategy strategy) {
            return BeanUtils.copyProperties(source, (T) new OriginalObject(), strategy);
        }
    };
    CopiedObject copied = copier.copy(original, strategy);
    // 现在你可以安全地修改copied对象,而不会影响原始对象或其他副本
}

2. 创建新对象

在向List添加元素时,确保每次都创建一个新的对象。这样可以避免对象引用的问题,因为每个对象都是独立的。

List<User> userList = new ArrayList<>();

for (int i = 0; i < 5; i++) {
    User user = new User(); // 在每次循环中创建新对象
    user.setId(i);
    user.setName("User " + i);
    userList.add(user); // 添加新对象到列表
}

3. 避免直接修改原对象

如果你需要修改对象,尽量保持对象的不可变性,或者在修改前创建一个副本。这样可以确保你的修改不会影响其他可能引用同一对象的地方。

public void avoidModifyingOriginal() {
    List<User> userList = ...; // 假设这是你的用户列表
    for (User user : userList) {
        // 避免直接修改user对象,而是创建一个新的副本进行修改
        User modifiedUser = new User(user); // 假设User有一个复制构造函数
        modifiedUser.setName("Modified " + modifiedUser.getName());
        // 现在你可以安全地使用modifiedUser,而不会影响原始的user对象
    }
}

以上三种方法都可以有效地避免在操作List对象时出现覆盖和共享引用的问题。选择哪种方法取决于你的具体需求和上下文。在实际开发中,通常建议尽可能使用不可变对象和深拷贝策略,这样可以减少潜在的错误和bug,提高代码的可维护性和健壮性。

四、 结论

在 Java 编程中,了解 List 的 add 方法和覆盖现象是至关重要的。通过遵循良好的设计原则和使用适当的技术手段,可以有效地避免覆盖现象带来的问题,提高程序的稳定性和可维护性。这些技巧对于编写高质量、健壮的 Java 代码至关重要,也是作为 Java 开发人员必备的重要知识点之一。

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

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

相关文章

MyBatis的基本应用

源码地址 01.MyBatis环境搭建 添加MyBatis的坐标 <!--mybatis坐标--><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.9</version></dependency><!--mysql驱动坐…

VSCode调试C++

1、环境准备 1.1、g的安装与使用 1.1.1、安装 方式一&#xff1a;Xcode安装 苹果的开发集成工具是Xcode.app&#xff0c;其中包含一堆命令行工具。 在 App store 可以看到其大小有好几个G&#xff0c;有点大。 方式二&#xff1a;Command Line Tools 安装 Command Line Too…

OpenHarmony实战:小型系统器件驱动移植

本章节讲解如何移植各类器件驱动。 LCD驱动移植 移植LCD驱动的主要工作是编写一个驱动&#xff0c;在驱动中生成模型的实例&#xff0c;并完成注册。 这些LCD的驱动被放置在源码目录//drivers/hdf_core/framework/model/display/driver/panel中。 创建Panel驱动 创建HDF驱动…

高级数据结构与算法习题(6)

一、单选题 1、In the Tic-tac-toe game, a "goodness" function of a position is defined as f(P)=Wcomputer​−Whuman​ where W is the number of potential wins at position P. In the following figure, O represents the computer and X the human. What i…

农业保险利用卫星遥感监测、理赔、农作物风险评估

​农业保险一直是农民和农业生产者面临的重要课题&#xff0c;而卫星遥感技术的不断发展正为农业保险带来全新的解决方案。通过高分辨率的卫星遥感监测&#xff0c;农业保险得以更精准、及时地评估农田状况&#xff0c;为农业经营者提供可靠的风险管理手段。 **1. 灾害监测与风…

2024年第三期丨全国高校大数据与人工智能师资研修班邀请函

2024年第三期 杭州线下班 数据采集与机器学习实战&#xff08;Python&#xff09; 线上班 八大专题 大模型技术与应用实战 数据采集与处理实战&#xff08;Python&八爪鱼&#xff09; 大数据分析与机器学习实战&#xff08;Python&#xff09; 商务数据分析实战&…

29.使线程以固定顺序输出结果

借助wait和notify方法控制线程以固定的顺序执行&#xff1a; /*** 控制输出字符的顺序&#xff0c;必须是固定顺序2,1* 采用wait-notify实现* param args*/public static void main(String[] args) {new Thread(() -> {synchronized (lock) {while (!isPrint2) {try {lock.…

【c++】STl-list使用list模拟实现

主页&#xff1a;醋溜马桶圈-CSDN博客 专栏&#xff1a;c_醋溜马桶圈的博客-CSDN博客 gitee&#xff1a;mnxcc (mnxcc) - Gitee.com 目录 1. list的介绍及使用 1.1 list的介绍 1.2 list的使用 1.2.1 list的构造 1.2.2 list iterator的使用 1.2.3 list capacity 1.2.4 …

【Java】CAS详解

一.什么是CAS CAS(compare and swap) 比较并且交换. CAS是一个cpu指令,是原子的不可再分.因此基于CAS就可以给我们编写多线程的代码提供了新的思路---->使用CAS就不用使用加锁,就不会牵扯到阻塞,也称为无锁化编程 下面是一个CAS的伪代码: address是一个内存地址,expectVal…

电脑便签软件怎么用 纯分享几款电脑便签软件

在当今快节奏的生活中&#xff0c;随时记录灵感、待办事项或重要信息变得越来越重要。电脑便签软件成为了我们日常生活中不可或缺的工具之一。本文将介绍几款常见的电脑便签软件&#xff0c;并分享它们的使用方法&#xff0c;帮助读者更好地管理信息和提高工作效率。 1. 敬业签…

【ESP32S3 Sense接入语音识别+MiniMax模型+TTS模块语音播报】

【ESP32S3 Sense接入语音识别MiniMax模型TTS模块语音播报】 1. 前言2. 功能模块概述2.1 语音接入2.2 大模型接入2.3 TTS模块接入 3. 先决条件3.1 环境配置3.2 所需零件3.3 硬件连接步骤 4. 核心代码4.1 源码分享4.2 代码解析 5. 上传验证5.1 对话测试5.2 报错 6. 总结 1. 前言 …

NumPy的ndarray常用属性和索引你学会了吗

1.ndarray的4个重要属性 ndim&#xff1a;返回数组的维度数。例如&#xff0c;一维数组的ndim为1&#xff0c;二维数组的ndim为2 shape&#xff1a;返回数组的形状&#xff0c;即各个维度的大小。例如&#xff0c;对于一个二维数组&#xff0c;shape会返回一个包含行数和列数的…

Docker in Docker原理与实战探索

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鸿蒙》 …

使用CyberRT写第一个代码

0. 简介 计算框架是自动驾驶系统中的重中之重&#xff0c;也是整个系统得以高效稳定运行的基础。为了实时地完成感知、决策和执行&#xff0c;系统需要一系列的模块相互紧密配合&#xff0c;高效地执行任务流。由于各种原因&#xff0c;这些模块可能位于不同进程&#xff0c;也…

Android Studio 打开Logcat界面

在平时调试过程中查看调试日志需要打开 Android Studio Logcat界面。 每次安装AS都会忘记&#xff0c;自己备注一下。 AS->View->Tool Windows->Logcat

Windows12安装Docker

环境及工具&#xff08;文末提供&#xff09; Docker Desktop Installer.exe &#xff08;官网&#xff09; 一、查看windows相关配置 查看是否开启相应的功能&#xff0c;如果没有需要开启&#xff0c;然后重启电脑 打开任务管理器&#xff08;CTRLSHIFTESC&#xff09;-&g…

【Linux】进程控制详解

目录 前言 进程创建 认识fork 写时拷贝 再谈fork 进程终止 进程退出码 用代码来终止进程 常见的进程终止的方式 exit _exit 进程等待 进程等待的必要性 进程等待的方式 wait waitpid 详解status参数 详解option参数 前言 本文适合有一点基础的人看的&#…

【JavaScript】函数 ⑥ ( 使用 arguments 获取所有实参 | arguments 内置对象 | 伪数组概念 )

文章目录 一、使用 arguments 获取所有实参1、arguments 内置对象2、伪数组概念3、arguments 实参遍历4、arguments 代码示例 - 基本使用5、arguments 代码示例 - 遍历实参 一、使用 arguments 获取所有实参 1、arguments 内置对象 在 定义 JavaScript 函数 时 , 有时 不确定 形…

Spring定义Bean对象笔记(二)

前言&#xff1a;上一篇记录了通过XML文件来定义Bean对象&#xff0c;这一篇将记录通过注解和配置类的方式来定义Bean对象。 核心注解&#xff1a; 定义对象&#xff1a;Component,Service,Repository,Controller 依赖注入&#xff1a; 按类型&#xff1a;Autowired 按名称&am…

cesium 加载mapbox底图 黑色主题底图 84底图

cesium提供MapboxStyleImageryProvider&#xff0c;加载mapbox的影像图层&#xff0c;底图是84坐标系。 viewer.imageryLayers.addImageryProvider(new Cesium.MapboxStyleImageryProvider({styleId: dark-v11,accessToken: mapbox的token})); 效果图&#xff1a;加载mapbox黑…