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

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

例如:

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

预期:

 0 1 2 

实际:

 2 2 2 

我犯了什么错误?

注意:这是针对本网站上出现的大量类似问题的规范问答。

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

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

  • 意外地将相同的对象添加到列表中

静态字段

如果列表中的对象将数据存储在静态字段中,则列表中的每个对象将显示为相同,因为它们保持相同的值。 考虑下面的类:

 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 。 (请参阅“了解class级成员”教程。)

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

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

解决方法很简单 – 除非实际上需要在该类的每个实例之间共享值,否则不要在类中使用static关键字。

添加相同的对象

如果将一个临时variables添加到列表中,则每次循环时都必须创build一个新实例。 考虑以下错误的代码片段:

 List<Foo> list = new ArrayList<Foo>(); Foo tmp = new Foo(); for (int i = 0; i < 3; i++) { tmp.setValue(i); list.add(tmp); } 

这里, tmp对象是在循环外部构build的。 结果, 相同的对象实例被添加到列表三次。 实例将保存值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); } 

你的问题是statictypes,每次循环迭代都需要一个新的初始化。 如果你在循环中,最好在循环中保持具体的初始化。

 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一个variables来检查上面代码片段的有效性; 你可以根据你的用例做一些其他的实现。

每次你添加一个对象到一个ArrayList,确保你添加一个新的对象,而不是已经使用的对象。 发生的事情是,当你添加相同的1个对象副本时,同一个对象被添加到ArrayList中的不同位置。 而当你改变一个,因为同样的副本被一遍又一遍的添加,所有的副本都会受到影响。 例如,假设你有一个像这样的ArrayList:

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

现在,如果您添加此卡c列表,它将被添加没有问题。 它将被保存在位置0处。但是,当你在列表中保存相同的卡片c时,它将被保存在位置1中。所以记住,你将相同的1个对象添加到列表中的两个不同位置。 现在,如果对卡片对象c进行更改,位于0和1的列表中的对象也将反映该更改,因为它们是同一个对象。

一种解决scheme是在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 } 

并且让我们说你有相同的卡片副本,所以在添加一个新的对象的时候,你可以这样做:

 list.add(new Card(nameOfTheCardObjectThatYouWantADifferentCopyOf)); 

与日历实例有同样的麻烦。

错误代码:

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

你必须创build一个新的日历对象,这可以用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); }