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

java list arraylist static


我在一个ArrayList中添加了三个不同的对象,但是这个列表包含了我添加的最后一个对象的三个副本。

例如:

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

Expected:

0
1
2

Actual:

2
2
2

我犯了什么错误?

注意:这是对本站点上发生的许多类似问题的规范问答。





Answer 1 Duncan Jones


这个问题有两个典型的原因。

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

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

静态领域

如果你的列表中的对象在静态字段中存储数据,那么你的列表中的每个对象看起来都是一样的,因为它们持有相同的值。考虑下图中的类。

public class Foo {
  private static int value; 
  //      ^^^^^^------------ - Here's the problem!

  public Foo(int value) {
    this.value = value;
  }

  public int getValue() {
    return value;
  }
}

在该示例中,只有一个 int valueFoo 的所有实例之间共享,因为它被声明为 static 。(请参阅“了解班级成员”教程。)

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

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);
}

在这里, tmp 对象是在循环外部构造的。结果,同一对象实例被添加到列表中三次。该实例将保留值 2 ,因为该值是在上次调用 setValue() 时传递的值。

要解决这个问题,只需在循环内移动对象构造即可。

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);
}



Answer 2 Shashank


您的问题是 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 中的一个变量,用于检查上述代码段的有效性;您可以根据用例进行其他实现。




Answer 3 basti12354


在日历实例中也遇到了同样的问题。

错误的代码。

Calendar myCalendar = Calendar.getInstance();

for (int days = 0; days < daysPerWeek; days++) {
    myCalendar.add(Calendar.DAY_OF_YEAR, 1);

    // In the next line lies the error
    Calendar newCal = myCalendar;
    calendarList.add(newCal);
}

您必须创建日历的NEW对象,可以使用 calendar.clone() 完成此操作。

Calendar myCalendar = Calendar.getInstance();

for (int days = 0; days < daysPerWeek; days++) {
    myCalendar.add(Calendar.DAY_OF_YEAR, 1);

    // RIGHT WAY
    Calendar newCal = (Calendar) myCalendar.clone();
    calendarList.add(newCal);

}



Answer 4 Faraz


每当你向ArrayList中添加一个对象时,要确保你添加的是一个新的对象而不是已经使用过的对象。发生的情况是,当你添加相同的1个对象的副本时,同一个对象会被添加到ArrayList中的不同位置。而当你对其中一个对象进行更改时,因为同一个副本被反复添加,所有的副本都会受到影响。例如,假设你有一个像这样的ArrayList。

ArrayList<Card> list = new ArrayList<Card>();
Card c = new Card();

现在,如果你把这张卡c添加到列表中,它将被添加到列表中,它将被保存在0的位置。但是,当你在列表中保存同一个Card c时,它将被保存在1的位置。所以请记住,你把同一个1对象添加到列表中的两个不同位置。现在,如果你对卡片对象c做了一个改变,在列表中的位置0和1的对象也会反映出这个改变,因为它们是同一个对象。

一个解决方案是在Card类中创建一个构造函数,接受另一个Card对象。然后在这个构造函数中,你可以这样设置属性。

public Card(Card c){
this.property1 = c.getProperty1();
this.property2 = c.getProperty2(); 
... //add all the properties that you have in this class Card this way
}

假设你有相同的1份卡,所以在添加新对象的时候,你可以这样做。

list.add(new Card(nameOfTheCardObjectThatYouWantADifferentCopyOf));