简体   繁体   English

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

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

I'm adding three different objects to an ArrayList, but the list contains three copies of the last object I added.我正在向 ArrayList 添加三个不同的对象,但该列表包含我添加的最后一个 object 的三个副本。

For example:例如:

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

Expected:预期的:

0
1
2

Actual:实际的:

2
2
2

What mistake have I made?我犯了什么错误?

Note: this is designed to be a canonical Q&A for the numerous similar issues that arise on this site.注意:这是针对本网站上出现的众多类似问题的规范问答。

This problem has two typical causes:此问题有两个典型原因:

  • Static fields used by the objects you stored in the list您存储在列表中的对象使用的静态字段

  • Accidentally adding the same object to the list不小心将相同的对象添加到列表中

Static Fields静态字段

If the objects in your list store data in static fields, each object in your list will appear to be the same because they hold the same values.如果列表中的对象将数据存储在静态字段中,则列表中的每个对象看起来都是相同的,因为它们具有相同的值。 Consider the class below:考虑下面的类:

public class Foo {
  private static int value; 
  //      ^^^^^^------------ - Here's the problem!
  
  public Foo(int value) {
    this.value = value;
  }
  
  public int getValue() {
    return value;
  }
}

In that example, there is only one int value which is shared between all instances of Foo because it is declared static .在该示例中,只有一个int valueFoo的所有实例之间共享,因为它被声明为static (See "Understanding Class Members" tutorial.) (参见“理解类成员”教程。)

If you add multiple Foo objects to a list using the code below, each instance will return 3 from a call to getValue() :如果使用下面的代码将多个Foo对象添加到列表中,每个实例将从对getValue()的调用中返回3

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

The solution is simple - don't use the static keywords for fields in your class unless you actually want the values shared between every instance of that class.解决方案很简单——不要对类中的字段使用static关键字,除非您确实希望在该类的每个实例之间共享值。

Adding the Same Object添加相同的对象

If you add a temporary variable to a list, you must create a new instance of the object you are adding, each time you loop.如果将临时变量添加到列表中,则每次循环时都必须创建要添加的对象的新实例。 Consider the following erroneous code snippet:考虑以下错误的代码片段:

List<Foo> list = new ArrayList<Foo>();    
Foo tmp = new Foo();

for (int i = 0; i < 3; i++) {
  tmp.setValue(i);
  list.add(tmp);
}

Here, the tmp object was constructed outside the loop.这里, tmp对象是在循环之外构造的。 As a result, the same object instance is being added to the list three times.结果,相同的对象实例被添加到列表中三次。 The instance will hold the value 2 , because that was the value passed during the last call to setValue() .该实例将保存值2 ,因为这是在最后一次调用setValue()期间传递的值。

To fix this, just move the object construction inside the loop:要解决这个问题,只需在循环内移动对象构造:

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

Your problem is with the type static which requires a new initialization every time a loop is iterated.您的问题是static类型,每次迭代循环时都需要进行新的初始化。 If you are in a loop it is better to keep the concrete initialization inside the loop.如果您处于循环中,最好将具体初始化保持在循环内。

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

Instead of:代替:

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
}

Here tag is a variable in SomeStaticClass to check the validity of the above snippet;这里的tagSomeStaticClass中的一个变量,用于检查上述代码段的有效性; you can have some other implementation based on your use case.您可以根据您的用例进行一些其他实现。

Had the same trouble with the calendar instance.日历实例也有同样的问题。

Wrong code:错误代码:

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

You have to create a NEW object of the calendar, which can be done with calendar.clone() ;你必须创建一个新的日历对象,这可以通过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);

}

Every time you add an object to an ArrayList, make sure you add a new object and not already used object.每次将对象添加到 ArrayList 时,请确保添加一个新对象而不是尚未使用的对象。 What is happening is that when you add the same 1 copy of object, that same object is added to different positions in an ArrayList.发生的情况是,当您添加相同的 1 个对象副本时,该对象将添加到 ArrayList 中的不同位置。 And when you make change to one, because the same copy is added over and over again, all the copies get affected.而当你对一个进行更改时,由于一遍又一遍地添加相同的副本,所有的副本都会受到影响。 For example, Say you have an ArrayList like this:例如,假设您有一个这样的 ArrayList:

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

Now if you add this Card c to list, it will be added no problem.现在,如果您将此卡 c 添加到列表中,则添加它没有问题。 It will be saved at location 0. But, when you save the same Card c in the list, it will be saved at location 1. So remember that you added same 1 object to two different locations in a list.它将保存在位置 0。但是,当您将相同的卡片 c 保存在列表中时,它将保存在位置 1。因此请记住,您将相同的 1 对象添加到列表中的两个不同位置。 Now if you make a change that Card object c, the objects in a list at location 0 and 1 will also reflect that change, because they are the same object.现在,如果您更改 Card 对象 c,列表中位置 0 和 1 的对象也将反映该更改,因为它们是同一个对象。

One solution would be to make a constructor in Card class, that accepts another Card object.一种解决方案是在 Card 类中创建一个构造函数,该构造函数接受另一个 Card 对象。 Then in that constructor, you can set the properties like this:然后在该构造函数中,您可以像这样设置属性:

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
}

And lets say you have the same 1 copy of Card, so at the time of adding a new object, you can do this:假设您拥有相同的 1 个 Card 副本,因此在添加新对象时,您可以这样做:

list.add(new Card(nameOfTheCardObjectThatYouWantADifferentCopyOf));

It can also consequence of using the same reference instead of using a new one.它也可能是使用相同引用而不是使用新引用的结果。

 List<Foo> list = new ArrayList<Foo>();        

 setdata();
......

public void setdata(int i) {
  Foo temp = new Foo();
  tmp.setValue(i);
  list.add(tmp);
}

Instead of:代替:

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