提问人:Duncan Jones 提问时间:11/8/2013 最后编辑:BraiamDuncan Jones 更新时间:4/28/2022 访问量:72871
为什么我的 ArrayList 包含添加到列表中的最后一项的 N 个副本?
Why does my ArrayList contain N copies of the last item added to the list?
问:
我将向 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
3
getValue()
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);
}
评论
tmp
tmp.setValue(2)
you must create a new instance each time you loop
Adding the same object
您的问题出在每次迭代循环时都需要重新初始化的类型。如果你在一个循环中,最好将具体的初始化保持在循环中。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
}
这是一个变量,用于检查上述代码片段的有效性;您可以根据自己的用例进行一些其他实现。tag
SomeStaticClass
评论
static
public class SomeClass{/*some code*/}
public static class SomeStaticClass{/*some code*/}
static
static
每次向 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));
评论
日历实例也有同样的问题。
错误的代码:
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);
}
您必须创建日历的 NEW 对象,这可以通过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
使用相同的引用而不是使用新的引用也可能是结果。
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);
}
评论