为什么我的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 。 (请参阅“了解类成员”教程。)

如果使用下面的代码将多个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 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 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一个变量,用于检查上述代码段的有效性; 您可以根据您的用例进行其他实现。

日历实例遇到同样的问题。

错误的代码:

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