मेरे ArrayList में सूची में जोड़े गए अंतिम आइटम की N प्रतियां क्यों हैं?


84

मैं एक ArrayList में तीन अलग-अलग ऑब्जेक्ट्स जोड़ रहा हूं, लेकिन सूची में मेरे द्वारा जोड़े गए अंतिम ऑब्जेक्ट की तीन प्रतियां हैं।

उदाहरण के लिए:

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

अपेक्षित होना:

0
1
2

वास्तविक:

2
2
2

मैंने क्या गलती की है?

नोट: यह इस साइट पर उत्पन्न होने वाले कई समान मुद्दों के लिए एक विहित क्यू एंड ए होने के लिए डिज़ाइन किया गया है।

जवाबों:


152

इस समस्या के दो विशिष्ट कारण हैं:

  • सूची में आपके द्वारा संग्रहित वस्तुओं द्वारा उपयोग किए जाने वाले स्थिर क्षेत्र

  • गलती से उसी ऑब्जेक्ट को सूची में जोड़ना

स्थैतिक क्षेत्र

यदि आपकी सूची में मौजूद वस्तुएं स्थिर क्षेत्रों में डेटा संग्रहीत करती हैं, तो आपकी सूची में प्रत्येक वस्तु समान दिखाई देगी क्योंकि वे समान मान रखती हैं। नीचे दिए गए वर्ग पर विचार करें:

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

1
hello @Duncan अच्छा समाधान, मैं आपसे पूछना चाहता हूं कि "समान वस्तु को जोड़ने" में तीन अलग-अलग उदाहरण मान 2 क्यों धारण करेंगे, क्या सभी तीन उदाहरणों में 3 अलग-अलग मान नहीं होने चाहिए? आशा है कि आप जल्द ही जवाब देंगे, धन्यवाद
देव

3
@Dev क्योंकि एक ही ऑब्जेक्ट ( tmp) सूची में तीन बार जोड़ा जाता है। और tmp.setValue(2)लूप के अंतिम पुनरावृत्ति में कॉल के कारण उस ऑब्जेक्ट का दो का मान है ।
डंकन जोन्स

1
ठीक है तो यहाँ समस्या एक ही वस्तु की वजह से है, क्या ऐसा है यदि मैं किसी सरणी सूची में तीन बार एक ही वस्तु जोड़ूँ, तो सरणी सूची obj के सभी तीन स्थान एक ही वस्तु को संदर्भित करेंगे?
देव

1
@ डेव यूप, बिल्कुल यही है।
डंकन जोन्स

1
एक स्पष्ट तथ्य जिसे मैंने शुरू में अनदेखा किया था: you must create a new instance each time you loopअनुभाग में भाग के विषय में Adding the same object: कृपया ध्यान दें कि आपके द्वारा जोड़ी जा रही वस्तु को चिन्हित करने के लिए संदर्भित उदाहरण, न कि जिस वस्तु को आप इसमें जोड़ रहे हैं।
पर

8

आपकी समस्या उस प्रकार के साथ है 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" से आपका क्या तात्पर्य है ? आपके लिए एक गैर-स्थिर वर्ग क्या होगा?
4castle

जैसे गैर-स्थैतिक: public class SomeClass{/*some code*/} और स्थैतिक public static class SomeStaticClass{/*some code*/}:। मुझे उम्मीद है कि अब यह स्पष्ट है।
शशांक

1
चूंकि, स्थिर वर्ग की सभी वस्तुएँ एक ही पते को साझा करती हैं, यदि वे एक लूप में आरंभीकृत होते हैं और प्रत्येक पुनरावृत्ति में विभिन्न मानों के साथ सेट किए जाते हैं। वे सभी समरूप मूल्य वाले होंगे जो अंतिम पुनरावृत्ति के मूल्य के बराबर होंगे या जब अंतिम वस्तु को संशोधित किया गया था। मुझे उम्मीद है कि अब यह स्पष्ट है।
शशांक

6

कैलेंडर उदाहरण के साथ एक ही परेशानी थी।

गलत कोड:

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

}

4

हर बार जब आप किसी ArrayList में कोई ऑब्जेक्ट जोड़ते हैं, तो सुनिश्चित करें कि आप एक नई ऑब्जेक्ट जोड़ते हैं और पहले से उपयोग की गई वस्तु नहीं। क्या हो रहा है कि जब आप एक ही वस्तु की 1 प्रति जोड़ते हैं, तो एक ही वस्तु को एक ArrayList में विभिन्न पदों में जोड़ा जाता है। और जब आप किसी एक में बदलाव करते हैं, क्योंकि एक ही कॉपी को बार-बार जोड़ा जाता है, तो सभी प्रतियां प्रभावित हो जाती हैं। उदाहरण के लिए, मान लें कि आपके पास इस तरह एक ArrayList है:

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

अब यदि आप इस कार्ड को सूची में जोड़ते हैं, तो इसे बिना किसी समस्या के जोड़ा जाएगा। यह स्थान 0. पर सहेजा जाएगा। लेकिन, जब आप सूची में एक ही कार्ड सी को सहेजते हैं, तो इसे स्थान 1 पर सहेजा जाएगा। इसलिए याद रखें कि आपने एक सूची में दो अलग-अलग स्थानों में एक ही वस्तु को जोड़ा है। अब यदि आप एक परिवर्तन करते हैं जो कार्ड ऑब्जेक्ट c, स्थान 0 और 1 की सूची में ऑब्जेक्ट भी उस परिवर्तन को प्रतिबिंबित करेगा, क्योंकि वे एक ही वस्तु हैं।

एक समाधान कार्ड क्लास में एक कंस्ट्रक्टर बनाने के लिए होगा, जो दूसरे कार्ड ऑब्जेक्ट को स्वीकार करता है। फिर उस कंस्ट्रक्टर में, आप इस तरह से गुण सेट कर सकते हैं:

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
}

और कह सकते हैं कि आपके पास कार्ड की एक ही प्रति है, इसलिए एक नई वस्तु जोड़ने के समय, आप यह कर सकते हैं:

list.add(new Card(nameOfTheCardObjectThatYouWantADifferentCopyOf));

2

यह एक नए का उपयोग करने के बजाय एक ही संदर्भ का उपयोग करने का भी परिणाम हो सकता है।

 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);
} 
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.