为什么我的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
。 (请参阅“了解类成员”教程。)
如果使用下面的代码将多个Foo
对象添加到列表中,则每个实例将从对getValue()
的调用返回3
:
for (int i = 0; i < 4; i++) { list.add(new Foo(i)); }
解决方案很简单 - 不要对类中的字段使用static
关键字,除非您确实希望在该类的每个实例之间共享值。
添加相同的对象
如果将临时变量添加到列表,则必须在每次循环时创建新实例。 请考虑以下错误的代码段:
List list = new ArrayList (); Foo tmp = new Foo(); for (int i = 0; i < 3; i++) { tmp.setValue(i); list.add(tmp); }
这里, tmp
对象是在循环外部构造的。 结果, 相同的对象实例被添加到列表三次。 实例将保留值2
,因为这是在上次调用setValue()
期间传递的值。
要解决此问题,只需在循环内移动对象构造:
List list = new ArrayList (); for (int i = 0; i < 3; i++) { Foo tmp = new Foo(); // <-- fresh instance! tmp.setValue(i); list.add(tmp); }
你的问题是static
类型,每次循环迭代时都需要新的初始化。 如果您处于循环中,最好将具体初始化保留在循环内。
List
代替:
List
这里tag
是SomeStaticClass
一个变量,用于检查上述代码段的有效性; 您可以根据您的用例进行其他实现。
日历实例遇到同样的问题。
错误的代码:
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); }
您必须创建日历的新对象,可以使用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); }
每次将对象添加到ArrayList时,请确保添加新对象而不是已使用的对象。 发生的事情是,当您添加相同的1个对象副本时,该相同的对象将添加到ArrayList中的不同位置。 当你改变一个,因为一遍又一遍地添加相同的副本,所有副本都会受到影响。 例如,假设你有一个像这样的ArrayList:
ArrayList list = new ArrayList (); 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 }
并且假设你有相同的1份卡片,所以在添加新对象时,你可以这样做:
list.add(new Card(nameOfTheCardObjectThatYouWantADifferentCopyOf));