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

Posted

技术标签:

【中文标题】为啥我的 ArrayList 包含添加到列表中的最后一项的 N 个副本?【英文标题】:Why does my ArrayList contain N copies of the last item added to the list?为什么我的 ArrayList 包含添加到列表中的最后一项的 N 个副本? 【发布时间】:2013-11-19 13:04:48 【问题描述】:

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

例如:

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

预期:

0
1
2

实际:

2
2
2

我犯了什么错误?

注意:这是针对本网站上出现的许多类似问题的规范问答。

【问题讨论】:

【参考方案1】:

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

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

【讨论】:

【参考方案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。 (参见"Understanding Class Members" 教程。)

如果您使用下面的代码将多个 Foo 对象添加到列表中,每个实例将从对 getValue() 的调用中返回 3

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

解决方案很简单 - 不要对类中的字段使用 static 关键字,除非您确实希望在该类的每个实例之间共享值。

添加相同的对象

如果将临时变量添加到列表中,则必须在每次循环时创建要添加的对象的新实例。考虑以下错误代码sn-p:

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

【讨论】:

你好@Duncan 很好的解决方案,我想问你,在“添加相同的对象”中,为什么三个不同的实例会保存值 2,不是所有三个实例都应该保存 3 个不同的值吗?希望您能尽快回复,谢谢 @Dev 因为同一个对象(tmp)被添加到列表中三次。该对象的值为 2,因为在循环的最后一次迭代中调用了 tmp.setValue(2) 好的,所以这里的问题是因为同一个对象,如果我在数组列表中添加三次相同的对象,arraylist obj 的所有三个位置都会引用同一个对象吗? @Dev 是的,就是这样。 我最初忽略了一个明显的事实:关于Adding the same object 部分中的you must create a new instance each time you loop 部分:请注意,引用的实例涉及您要添加的对象,而不是您要添加的对象。 【参考方案3】:

日历实例也有同样的问题。

错误代码:

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


【讨论】:

致未来的读者:你根本不应该使用Calendar【参考方案4】:

每次将对象添加到 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));

【讨论】:

亲爱的@Faraz 这是一个错误,不是吗?我认为java程序员必须纠正这一点。我的问题here (specially, see final lines of question). 中还有另一种问题 问题在于“某些”类型的对象!不是全部。例如字符串没有问题。而字符串数组有问题! 我亲爱的兄弟 Mohammad Hosein,Assalam o Alaikum。字符串没有这个问题的原因是,字符串是不可变的。每次修改字符串时,它都会在内部创建一个新字符串并返回它。这就是为什么您看不到字符串的问题。 我得回去睡觉了。当我醒来时,我会试着看看你的问题。 va alaikom alsalaam 非常感谢@Faraz 兄弟。有一个很好的睡眠。我得到了我的答案(特别是你的这个答案)。只有我想要这些引用/链接的帮助/教程......以避免进一步的问题。 (我不知道我应该在谷歌搜索什么?)谢谢【参考方案5】:

您的问题在于 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中的一个变量,用来检查上面sn -p的有效性;你可以根据你的用例有一些其他的实现。

【讨论】:

“输入static”是什么意思?对你来说什么是非静态类? 例如非静态:public class SomeClass/*some code*/ 和静态:public static class SomeStaticClass/*some code*/。我希望现在更清楚了。 因为静态类的所有对象共享相同的地址,如果它们在循环中初始化并且在每次迭代中设置为不同的值。所有这些最终都将具有相同的值,这将等于最后一次迭代的值或最后一个对象被修改时的值。我希望现在更清楚了。 因为静态类的所有对象共享相同的地址, -> 这在 Java 的上下文中是完全错误的。纯 static 类在 java 中不存在,您可以将 static 添加到嵌套类中,但这不会自动使其成为单个实例/相同引用...

以上是关于为啥我的 ArrayList 包含添加到列表中的最后一项的 N 个副本?的主要内容,如果未能解决你的问题,请参考以下文章

为啥不将 FirebaseDatabase 引用中的所有数据都添加到 ArrayList<String> 中?

在 Android 中保存 ArrayList 的最简单方法是啥?

使用 Arraylist 将图标添加到 Listview

检查arraylist对象是不是存在

为啥我的 ListView 是从下到上开始的?

如何将文件的内容添加到数组列表中