文章目录
- 引言
- 一、背景介绍
- 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的引用到列表
在这个示例中,user1
和user2
是两个不同的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 开发人员必备的重要知识点之一。