为什么我的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 value
被Foo
所有实例共享,因为它被声明为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); }
你的问题是static
types,每次循环迭代都需要一个新的初始化。 如果你在循环中,最好在循环中保持具体的初始化。
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
一个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); }