繁体   English   中英

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

[英]Why does my ArrayList contain N copies of the last item added to the list?

我正在向 ArrayList 添加三个不同的对象,但该列表包含我添加的最后一个 object 的三个副本。

例如:

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

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

这里的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<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
}

假设您拥有相同的 1 个 Card 副本,因此在添加新对象时,您可以这样做:

list.add(new Card(nameOfTheCardObjectThatYouWantADifferentCopyOf));

它也可能是使用相同引用而不是使用新引用的结果。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM