簡體   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