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


83

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

例如:

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

预期:

0
1
2

实际:

2
2
2

我犯了什么错误?

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

Answers:


146

此问题有两个典型的原因:

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

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

静态场

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

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使用以下代码将多个对象添加到列表中,则每个实例都会3从调用返回getValue()

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

1
您好@Duncan好的解决方案,我想问您在“添加相同对象”中,为什么三个不同的实例将拥有值2,不是所有三个实例都应拥有3个不同的值吗?希望您能尽快回复,谢谢
开发人员2014年

3
@Dev因为同一对象(tmp)被添加到列表了三次。该对象的值为2,这是因为tmp.setValue(2)在循环的最终迭代中调用。
邓肯·琼斯

1
好的,所以这里的问题是由于相同的对象,如果我在数组列表中添加三次相同的对象,那么arraylist obj的所有三个位置都将引用同一个对象吗?
2014年

1
@Dev是的,就是这样。
邓肯琼斯

1
我最初忽略了一个明显的事实:关于本you must create a new instance each time you loop节中的部分Adding the same object:请注意,引用的实例涉及您要添加的对象,而不是您要添加对象的对象。

8

您的问题是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
}

tag是一个变量,SomeStaticClass用于检查以上代码段的有效性;您可以根据用例进行其他实现。


您所说的“类型static”是什么意思?对您来说,什么是非静态课程?
4castle

例如,非静态:public class SomeClass{/*some code*/} &静态:public static class SomeStaticClass{/*some code*/}。我希望现在更加清楚。
Shashank

1
由于静态类的所有对象共享一个地址,因此如果它们在循环中初始化并且在每次迭代中设置为不同的值。所有这些最终将具有相同的值,该值将等于上次迭代或修改最后一个对象时的值。我希望现在更加清楚。
Shashank

6

日历实例也遇到了同样的麻烦。

错误的代码:

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

}

4

每次将对象添加到ArrayList时,请确保添加新对象并且尚未使用过的对象。发生的情况是,当您添加对象的相同1个副本时,该对象将被添加到ArrayList中的不同位置。而且,当您更改为一个时,由于一次又一次地添加相同的副本,因此所有副本都会受到影响。例如,假设您有一个像这样的ArrayList:

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

现在,如果将此卡c添加到列表中,则不会有任何问题。它会保存在位置0。但是,当您将同一张卡片c保存在列表中时,它将保存在位置1。因此请记住,您将相同的1对象添加到了列表中的两个不同位置。现在,如果您对Card对象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
}

并假设您拥有Card的1个副本,因此在添加新对象时,您可以执行以下操作:

list.add(new Card(nameOfTheCardObjectThatYouWantADifferentCopyOf));

2

使用相同的引用而不是使用新的引用也会导致这种情况。

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

 setdata();
......

public void setdata(int i) {
  Foo temp = new Foo();
  tmp.setValue(i);
  list.add(tmp);
}

代替:

List<Foo> list = new ArrayList<Foo>(); 
Foo temp = new Foo();       
setdata();
......

public void setdata(int i) {
  tmp.setValue(i);
  list.add(tmp);
} 
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.