为什么我的 ArrayList 包含添加到列表中的最后一项的 N 个副本?

2022-08-31 10:34:27

我正在向 ArrayList 添加三个不同的对象,但该列表包含我添加的最后一个对象的三个副本。

例如:

for (Foo f : list) {
  System.out.println(f.getValue());
}    

预期:

0
1
2

实际:

2
2
2

我犯了什么错误?

注意:这旨在成为本网站上出现的众多类似问题的规范问答。


答案 1

此问题有两个典型原因:

  • 存储在列表中的对象所使用的静态字段

  • 意外将一对象添加到列表中

静态字段

如果列表中的对象在静态字段中存储数据,则列表中的每个对象看起来都相同,因为它们包含相同的值。考虑以下类:

public class Foo {
  private static int value; 
  //      ^^^^^^------------ - Here's the problem!
  
  public Foo(int value) {
    this.value = value;
  }
  
  public int getValue() {
    return value;
  }
}

在该示例中,只有一个实例在所有实例之间共享,因为它已声明。(请参阅“了解类成员”教程。int valueFoostatic

如果使用以下代码将多个对象添加到列表中,则每个实例将从对 以下的调用返回:Foo3getValue()

for (int i = 0; i < 4; i++) {      
  list.add(new Foo(i));
}

解决方案很简单 - 不要对类中的字段使用关键字,除非您确实希望在该类的每个实例之间共享值。static

添加相同的对象

如果将临时变量添加到列表中,则必须在每次循环时为要添加的对象创建一个新实例。请考虑以下错误的代码片段:

List<Foo> list = new ArrayList<Foo>();    
Foo tmp = new Foo();

for (int i = 0; i < 3; i++) {
  tmp.setValue(i);
  list.add(tmp);
}

此处,对象是在循环外部构造的。因此,同一对象实例将三次添加到列表中。实例将保存值 ,因为这是上次调用 期间传递的值。tmp2setValue()

要解决此问题,只需将对象构造移动到循环中:

List<Foo> list = new ArrayList<Foo>();        

for (int i = 0; i < 3; i++) {
  Foo tmp = new Foo(); // <-- fresh instance!
  tmp.setValue(i);
  list.add(tmp);
}

答案 2

您的问题在于每次迭代循环时都需要新初始化的类型。如果你在一个循环中,最好将具体的初始化保留在循环中。static

List<Object> objects = new ArrayList<>(); 

for (int i = 0; i < length_you_want; i++) {
    SomeStaticClass myStaticObject = new SomeStaticClass();
    myStaticObject.tag = i;
    // Do stuff with myStaticObject
    objects.add(myStaticClass);
}

而不是:

List<Object> objects = new ArrayList<>(); 

SomeStaticClass myStaticObject = new SomeStaticClass();
for (int i = 0; i < length; i++) {
    myStaticObject.tag = i;
    // Do stuff with myStaticObject
    objects.add(myStaticClass);
    // This will duplicate the last item "length" times
}

这里有一个变量,用于检查上述片段的有效性;您可以根据您的使用案例进行其他一些实现。tagSomeStaticClass


推荐