Neden ArrayList'im listeye eklenen son öğenin N kopyasını içeriyor?


84

Bir ArrayList'e üç farklı nesne ekliyorum, ancak liste eklediğim son nesnenin üç kopyasını içeriyor.

Örneğin:

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

Beklenen:

0
1
2

Gerçek:

2
2
2

Ne hata yaptım?

Not: Bu, bu sitede ortaya çıkan çok sayıda benzer sorun için kanonik bir Soru-Cevap olarak tasarlanmıştır.

Yanıtlar:


152

Bu sorunun iki tipik nedeni vardır:

  • Listede sakladığınız nesneler tarafından kullanılan statik alanlar

  • Yanlışlıkla aynı nesneyi listeye eklemek

Statik Alanlar

Listenizdeki nesneler verileri statik alanlarda depoluyorsa, listenizdeki her nesne aynı değerleri taşıdıkları için aynı görünecektir. Aşağıdaki sınıfı düşünün:

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

Bu örnekte, bildirildiği için int valuetüm örnekler arasında paylaşılan yalnızca bir tane var . ( "Sınıf Üyelerini Anlama" eğiticisine bakın.)Foostatic

FooAşağıdaki kodu kullanarak bir listeye birden çok nesne eklerseniz , her örnek 3bir çağrıdan şuna geri dönecektir getValue():

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

Çözüm basit - staticdeğerlerin gerçekten o sınıfın her örneği arasında paylaşılmasını istemiyorsanız, sınıfınızdaki alanlar için anahtar kelimeleri kullanmayın .

Aynı Nesneyi Eklemek

Bir listeye geçici bir değişken eklerseniz, her döngüde eklediğiniz nesnenin yeni bir örneğini oluşturmanız gerekir. Aşağıdaki hatalı kod pasajını düşünün:

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

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

Burada tmpnesne döngünün dışında oluşturulmuştur. Sonuç olarak, aynı nesne örneği listeye üç kez ekleniyor. Örnek değeri tutacaktır 2, çünkü son çağrı sırasında aktarılan değer buydu setValue().

Bunu düzeltmek için, nesne yapısını döngünün içine taşıyın:

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

1
merhaba @Duncan güzel çözüm, "Aynı Nesneyi Ekleme" bölümünde neden üç farklı örneğin 2 değerini tutacağını sormak istiyorum, her üç örnek de 3 farklı değer taşımamalı mı? yakında cevap vereceğinizi umuyoruz, teşekkürler
Dev

3
@Dev Çünkü aynı nesne ( tmp) listeye üç kez eklendi. tmp.setValue(2)Döngünün son yinelemesindeki çağrısı nedeniyle bu nesnenin değeri iki olur .
Duncan Jones

1
ok yani burada sorun aynı nesneden kaynaklanıyor, Öyleyse bir dizi listesine aynı nesneyi üç kez eklersem, dizi listesinin üç konumu da aynı nesneye başvurur?
Dev

1
@Dev Yup, aynen öyle.
Duncan Jones

1
Başlangıçta gözden kaçırdığım açık bir gerçek: you must create a new instance each time you loopbölümdeki bölümle ilgili olarak Adding the same object: Lütfen atıfta bulunulan örneğin eklediğiniz nesne ile değil, eklediğiniz nesne ile ilgili olduğunu unutmayın.
En

8

Sizin sorununuz, staticbir döngü her yinelendiğinde yeni bir başlatma gerektiren türdedir. Bir döngü içindeyseniz, somut başlatmayı döngü içinde tutmak daha iyidir.

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

Onun yerine:

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
}

İşte yukarıdaki kod parçasının geçerliliğini kontrol etmek için tagbir değişken SomeStaticClass; kullanım durumunuza bağlı olarak başka bir uygulamaya sahip olabilirsiniz.


"Tip static" ile ne demek istiyorsun ? Sizin için statik olmayan bir sınıf nedir?
4castle

örneğin statik olmayan: public class SomeClass{/*some code*/} & Statik: public static class SomeStaticClass{/*some code*/}. Umarım şimdi daha nettir.
Shashank

1
Statik bir sınıfın tüm nesneleri, bir döngüde başlatılırsa ve her yinelemede farklı değerlerle ayarlanırsa aynı adresi paylaşır. Hepsi, son yinelemenin değerine veya son nesnenin değiştirildiği zamana eşit olacak aynı değere sahip olacak. Umarım şimdi daha nettir.
Shashank

6

Takvim örneğiyle aynı sorunu yaşadım.

Yanlış kod:

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

Takvimin YENİ bir nesnesini oluşturmalısınız, bunu aşağıdakilerle yapabilirsiniz 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);

}

4

Bir ArrayList'e her nesne eklediğinizde, yeni bir nesne eklediğinizden ve daha önce kullanılmamış bir nesne eklediğinizden emin olun. Olan şey, nesnenin aynı 1 kopyasını eklediğinizde, aynı nesnenin bir ArrayList'teki farklı konumlara eklenmesidir. Ve birinde değişiklik yaptığınızda, aynı kopya defalarca eklendiği için, tüm kopyalar etkilenir. Örneğin, bunun gibi bir ArrayList'iniz olduğunu varsayalım:

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

Şimdi bu Kart c'yi listeye eklerseniz, hiçbir sorun eklenmeyecektir. Bu, 0 konumuna kaydedilecektir. Ancak, aynı Kart c'yi listeye kaydettiğinizde, konum 1'e kaydedilecektir. Bu nedenle, aynı 1 nesneyi bir listedeki iki farklı konuma eklediğinizi unutmayın. Şimdi, Kart nesnesi c'de bir değişiklik yaparsanız, 0 ve 1 konumundaki bir listedeki nesneler de bu değişikliği yansıtacaktır, çünkü bunlar aynı nesnedir.

Çözümlerden biri, Card sınıfında başka bir Card nesnesini kabul eden bir kurucu yapmak olabilir. Daha sonra bu yapıcıda özellikleri şu şekilde ayarlayabilirsiniz:

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
}

Ve diyelim ki Card'ın aynı 1 kopyasına sahipsiniz, bu nedenle yeni bir nesne eklerken bunu yapabilirsiniz:

list.add(new Card(nameOfTheCardObjectThatYouWantADifferentCopyOf));

2

Yeni bir referans kullanmak yerine aynı referansı kullanmaktan da kaynaklanabilir.

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

 setdata();
......

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

Onun yerine:

List<Foo> list = new ArrayList<Foo>(); 
Foo temp = new Foo();       
setdata();
......

public void setdata(int i) {
  tmp.setValue(i);
  list.add(tmp);
} 
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.