जब एक ArrayList से तत्वों को पुनरावृत्त करना और हटाना हो तो java.util.ConcurrentModificationException से कैसे बचें।


203

मेरे पास एक ArrayList है जिसे मैं अधिक पुनरावृत्त करना चाहता हूं। इस पर पुनरावृत्ति करते समय मुझे उसी समय तत्वों को निकालना होगा। जाहिर है कि यह फेंकता है java.util.ConcurrentModificationException

इस समस्या को संभालने के लिए सबसे अच्छा अभ्यास क्या है? क्या मुझे पहले सूची को क्लोन करना चाहिए?

मैं तत्वों को लूप में नहीं बल्कि कोड के दूसरे भाग से हटा देता हूं।

मेरा कोड इस तरह दिखता है:

public class Test() {
    private ArrayList<A> abc = new ArrayList<A>();

    public void doStuff() {
        for (A a : abc) 
        a.doSomething();
    }

    public void removeA(A a) {
        abc.remove(a);
    }
}

a.doSomethingफोन कर सकते हैं Test.removeA();


जवाबों:


325

दो विकल्प:

  • उन मानों की एक सूची बनाएं जिन्हें आप निकालना चाहते हैं, लूप के भीतर उस सूची में जोड़कर , फिर originalList.removeAll(valuesToRemove)अंत में कॉल करें
  • remove()पुनरावृत्त पर विधि का उपयोग करें । ध्यान दें कि इसका मतलब है कि आप लूप के लिए संवर्धित का उपयोग नहीं कर सकते हैं।

दूसरे विकल्प के एक उदाहरण के रूप में, किसी सूची से 5 से अधिक लंबाई के साथ किसी भी तार को हटाना:

List<String> list = new ArrayList<String>();
...
for (Iterator<String> iterator = list.iterator(); iterator.hasNext(); ) {
    String value = iterator.next();
    if (value.length() > 5) {
        iterator.remove();
    }
}

2
मुझे यह उल्लेख करना चाहिए कि मैं कोड के किसी अन्य भाग में तत्वों को हटाता हूं, न कि स्वयं लूप।
RoflcoptrException

@Roflcoptr: यह देखना मुश्किल है कि कोड के दो बिट्स कैसे इंटरैक्ट करते हैं। असल में, आप ऐसा नहीं कर सकते। यह स्पष्ट नहीं है कि पहले सूची को क्लोन करना मदद करेगा, बिना यह देखे कि यह सब एक साथ कैसे लटका हुआ है। क्या आप अपने प्रश्न में अधिक विवरण दे सकते हैं?
जॉन स्कीट

मुझे पता है कि सूची का क्लोन बनाने में मदद मिलेगी, लेकिन मुझे नहीं पता कि यह एक अच्छा तरीका है। लेकिन मैं कुछ और कोड जोड़ूंगा।
RoflcoptrException

2
यह समाधान java.util.ConcurrentModificationException की ओर भी जाता है, stackoverflow.com/a/18448699/2914140 देखें ।
कूलमैड

1
@CoolMind: कई थ्रेड्स के बिना, यह कोड ठीक होना चाहिए।
जॉन स्कीट

17

ArrayList के JavaDocs से

इस वर्ग के पुनरावृत्त और सूचीकर्ता विधियों द्वारा दिए गए पुनरावृत्तियाँ विफल-तेज़ हैं: यदि पुनरावृत्त बनाने के बाद सूची को किसी भी समय संरचनात्मक रूप से संशोधित किया जाता है, तो किसी भी तरह से, पुनरावृत्तिकर्ता के स्वयं के निष्कासन या जोड़ने के तरीकों के अलावा , यह शोधकर्ता एक समवर्तीमॉडिफिकेशन अपवाद को फेंक देगा।


6
और सवाल का जवाब कहां है?
Adelin

जैसा कि यह कहता है, इटरेटर के स्वयं के हटाने या जोड़ने के तरीकों को छोड़कर
वरुण अचर

14

आप उन्नत "लूप" के लिए सूची से मूल्य निकालने की कोशिश कर रहे हैं, जो कि संभव नहीं है, भले ही आप कोई भी ट्रिक लागू करें (जो आपने अपने कोड में किया था)। बेहतर तरीका यह है कि इटरेटर स्तर को अन्य सलाह के अनुसार कोड किया जाए।

मुझे आश्चर्य है कि कैसे लोगों ने पाश दृष्टिकोण के लिए पारंपरिक सुझाव नहीं दिया है।

for( int i = 0; i < lStringList.size(); i++ )
{
    String lValue = lStringList.get( i );
    if(lValue.equals("_Not_Required"))
    {
         lStringList.remove(lValue);
         i--; 
    }  
}

यह भी काम करता है।


2
यह सही नहीं है!!! जब आप किसी तत्व को हटाते हैं, तो अगला उसकी स्थिति ले रहा होता है और जब मैं बढ़ाता हूं तो अगले तत्व को अगले पुनरावृत्ति में चेक नहीं किया जाता है। इस मामले में आपको जाना चाहिए (int i = lStringList.size (?; I> -1;
i--

1
इस बात से सहमत! वैकल्पिक है - i प्रदर्शन करना; अगर लूप के लिए स्थिति में है।
suhas0sn07

मुझे लगता है कि यह उत्तर उपरोक्त टिप्पणियों में मुद्दों को संबोधित करने के लिए संपादित किया गया था, इसलिए अब यह ठीक है, कम से कम मेरे लिए।
किरा रेसर

11

आपको वास्तव में पारंपरिक तरीके से सरणी को वापस करना चाहिए

हर बार जब आप सूची से किसी तत्व को निकालते हैं, उसके बाद के तत्व आगे धकेल दिए जाएंगे। जब तक आप पुनरावृत्ति के अलावा अन्य तत्वों को नहीं बदलते हैं, तब तक निम्न कोड काम करना चाहिए।

public class Test(){
    private ArrayList<A> abc = new ArrayList<A>();

    public void doStuff(){
        for(int i = (abc.size() - 1); i >= 0; i--) 
            abc.get(i).doSomething();
    }

    public void removeA(A a){
        abc.remove(a);
    }
}

10

Java 8 में आप Collection Interface का उपयोग कर सकते हैं और इसे removeIf विधि कहकर कर सकते हैं:

yourList.removeIf((A a) -> a.value == 2);

अधिक जानकारी यहां पाई जा सकती है


6

लूप को सामान्य तरीके से करें, java.util.ConcurrentModificationExceptionएक त्रुटि है जो एक्सेस किए गए तत्वों से संबंधित है।

इसलिए कोशिश करें:

for(int i = 0; i < list.size(); i++){
    lista.get(i).action();
}

आपने java.util.ConcurrentModificationExceptionसूची में से कुछ भी नहीं निकालने से परहेज किया । मुश्किल। :) किसी सूची को पुनरावृत्त करने के लिए आप वास्तव में इसे "सामान्य तरीका" नहीं कह सकते।
Zsolt Sky

6

सूची को पुनरावृत्त करते समय, यदि आप तत्व को निकालना चाहते हैं तो संभव है। मेरे उदाहरण नीचे देखें,

ArrayList<String>  names = new ArrayList<String>();
        names.add("abc");
        names.add("def");
        names.add("ghi");
        names.add("xyz");

मेरे पास एरे सूची के उपरोक्त नाम हैं। और मैं उपरोक्त सूची से "डीफ़" नाम हटाना चाहता हूं,

for(String name : names){
    if(name.equals("def")){
        names.remove("def");
    }
}

उपरोक्त कोड ConcurrentModificationException अपवाद को फेंकता है क्योंकि आप पुनरावृत्ति करते समय सूची को संशोधित कर रहे हैं।

तो, इस तरह से करके Arraylist से "डीफ़" नाम को हटा दें,

Iterator<String> itr = names.iterator();            
while(itr.hasNext()){
    String name = itr.next();
    if(name.equals("def")){
        itr.remove();
    }
}

उपरोक्त कोड, इट्रेटर के माध्यम से हम Arraylist से "डीफ़" नाम को हटा सकते हैं और सरणी को प्रिंट करने का प्रयास कर सकते हैं, आपको नीचे आउटपुट दिखाई देगा।

आउटपुट: [abc, ghi, xyz]


एल्स, हम समवर्ती सूची का उपयोग कर सकते हैं जो समवर्ती पैकेज में उपलब्ध है, ताकि आप इसे हटाते समय ऑपरेशन निकाल सकें और जोड़ सकें। उदाहरण के लिए नीचे दिए गए कोड स्निपेट को देखें। ArrayList <string> name = new ArrayList <string> (); CopyOnWriteArrayList <string> copyNames = new CopyOnWriteArrayList <स्ट्रिंग> (नाम); के लिए (स्ट्रिंग नाम: copyNames) {if (name.equals ("def")) {copyNames.remove ("डिफ़"); }}
इंद्र के

CopyOnWriteArrayList महंगा ऑपरेशन होने वाला है।
इंद्र के

5

एक विकल्प यह है कि removeAविधि को संशोधित किया जाए -

public void removeA(A a,Iterator<A> iterator) {
     iterator.remove(a);
     }

लेकिन इसका मतलब यह आपके हैं doSomething()पारित करने के लिए सक्षम होना चाहिए iteratorकरने के लिए removeविधि। बहुत अच्छा विचार नहीं है।

क्या आप इसे दो चरणों में कर सकते हैं: पहले लूप में जब आप सूची पर पुनरावृति करते हैं, तो चयनित तत्वों को हटाने के बजाय, उन्हें हटाए जाने के रूप में चिह्नित करें । इसके लिए, आप बस इन तत्वों (उथले प्रतिलिपि) को दूसरे में कॉपी कर सकते हैं ।List

फिर, एक बार जब आपका पुनरावृत्ति हो जाता है, तो removeAllपहली सूची से दूसरी सूची के सभी तत्वों को करें।


उत्कृष्ट, मैंने एक ही दृष्टिकोण का उपयोग किया, हालांकि मैं दो बार लूप करता हूं। यह चीजों को सरल बनाता है और इसके साथ कोई समवर्ती मुद्दे नहीं है :)
पंकज निमगडे

1
मैं नहीं देखता कि Iterator में एक (ए) विधि है। निष्कासन () कोई तर्क नहीं लेता है docs.oracle.com/javase/8/docs/api/java/util/Iterator.html मैं क्या याद कर रहा हूं?
c0der

5

यहां एक उदाहरण है जहां मैं हटाने के लिए वस्तुओं को जोड़ने के लिए एक अलग सूची का उपयोग करता हूं, फिर बाद में मैं मूल सूची के तत्वों को हटाने के लिए stream.foreach का उपयोग करता हूं:

private ObservableList<CustomerTableEntry> customersTableViewItems = FXCollections.observableArrayList();
...
private void removeOutdatedRowsElementsFromCustomerView()
{
    ObjectProperty<TimeStamp> currentTimestamp = new SimpleObjectProperty<>(TimeStamp.getCurrentTime());
    long diff;
    long diffSeconds;
    List<Object> objectsToRemove = new ArrayList<>();
    for(CustomerTableEntry item: customersTableViewItems) {
        diff = currentTimestamp.getValue().getTime() - item.timestamp.getValue().getTime();
        diffSeconds = diff / 1000 % 60;
        if(diffSeconds > 10) {
            // Element has been idle for too long, meaning no communication, hence remove it
            System.out.printf("- Idle element [%s] - will be removed\n", item.getUserName());
            objectsToRemove.add(item);
        }
    }
    objectsToRemove.stream().forEach(o -> customersTableViewItems.remove(o));
}

मुझे लगता है कि आप दो छोरों को निष्पादित करने के लिए अतिरिक्त काम कर रहे हैं, सबसे खराब स्थिति में छोरों की पूरी सूची होगी। केवल एक लूप में इसे करना सबसे सरल और कम खर्चीला होगा।
लुइस कार्लोस

मुझे नहीं लगता कि आप पहले लूप के भीतर से ऑब्जेक्ट को हटा सकते हैं, इसलिए अतिरिक्त हटाने वाले लूप की आवश्यकता है, लूप को हटाने की भी केवल हटाने के लिए ऑब्जेक्ट हैं - शायद आप केवल एक लूप के साथ एक उदाहरण लिख सकते हैं, मैं इसे देखना चाहूंगा - धन्यवाद @ लुइसकार्लो
सर्प

जैसा कि आप इस कोड के साथ कहते हैं कि आप for-loop के अंदर कोई तत्व नहीं निकाल सकते क्योंकि यह java.util.ConcurrentModificationException अपवाद का कारण बनता है। हालाँकि आप इसके लिए एक मूल का उपयोग कर सकते हैं। यहाँ मैं आपके कोड के भाग का उपयोग करके एक उदाहरण लिखता हूँ।
लुइस कार्लोस

1
for (int i = 0; मैं <customersTableViewItems.size (); i ++) {diff = currentTimestamp.getValue ()। getTime () - customersTableViewItems.get (i) .timestamp.getValue ()। getTime () diffSeconds = diff / 1000% 60; if (diffSeconds> 10) {customersTableViewItems.remove (i--); }} महत्वपूर्ण i-- है क्योंकि आप किसी भी वृद्धि को छोड़ना नहीं चाहते हैं। इसके अलावा, आप ArrayList वर्ग द्वारा प्रदान की गई विधि removeIf (Predicate <? सुपर E> फ़िल्टर) का उपयोग कर सकते हैं। उम्मीद है कि यह मदद
लुइस कार्लोस

1
अपवाद इसलिए होता है क्योंकि सूची के पुनरावृत्त के लिए एक सक्रिय संदर्भ के रूप में लूप के लिए। सामान्य में, कोई संदर्भ नहीं है और आपके पास डेटा को बदलने के लिए अधिक लचीलापन है। आशा है कि इस मदद
लुइस कार्लोस

3

उपयोग करने के बजाय प्रत्येक लूप के लिए, लूप के लिए सामान्य का उपयोग करें। उदाहरण के लिए, नीचे दिया गया कोड java.util.ConcurrentModificationException दिए बिना सरणी सूची के सभी तत्व को हटा देता है। आप अपने उपयोग के मामले के अनुसार लूप में स्थिति को संशोधित कर सकते हैं।

   for(int i=0;i<abc.size();i++)  {

          e.remove(i);
        }

2

कुछ इस तरह से सरल करें:

for (Object object: (ArrayList<String>) list.clone()) {
    list.remove(object);
}

2

एक वैकल्पिक जावा 8 स्ट्रीम का उपयोग कर समाधान:

        theList = theList.stream()
            .filter(element -> !shouldBeRemoved(element))
            .collect(Collectors.toList());

जावा 7 में आप इसके बजाय अमरूद का उपयोग कर सकते हैं:

        theList = FluentIterable.from(theList)
            .filter(new Predicate<String>() {
                @Override
                public boolean apply(String element) {
                    return !shouldBeRemoved(element);
                }
            })
            .toImmutableList();

ध्यान दें, अमरूद उदाहरण एक अपरिवर्तनीय सूची में परिणाम करता है जो आप चाहते हैं या नहीं हो सकता है।


1

आप ArrayList के बजाय CopyOnWriteArrayList का भी उपयोग कर सकते हैं। यह JDK 1.5 के बाद से नवीनतम अनुशंसित दृष्टिकोण है।


1

मेरे मामले में, स्वीकृत उत्तर काम नहीं कर रहा है, यह अपवाद को रोकता है लेकिन यह मेरी सूची में कुछ असंगति का कारण बनता है। निम्नलिखित समाधान पूरी तरह से मेरे लिए काम कर रहा है।

List<String> list = new ArrayList<>();
List<String> itemsToRemove = new ArrayList<>();

for (String value: list) {
   if (value.length() > 5) { // your condition
       itemsToRemove.add(value);
   }
}
list.removeAll(itemsToRemove);

इस कोड में, मैंने हटाने के लिए आइटम को एक अन्य सूची में जोड़ा है और फिर list.removeAllसभी आवश्यक वस्तुओं को हटाने के लिए विधि का उपयोग किया है ।


0

"क्या मुझे सूची को पहले क्लोन करना चाहिए?"

यह सबसे आसान समाधान होगा, क्लोन से हटा दें, और हटाने के बाद क्लोन वापस कॉपी करें।

मेरे रूममब गेम से एक उदाहरण:

SuppressWarnings("unchecked")
public void removeStones() {
  ArrayList<Stone> clone = (ArrayList<Stone>) stones.clone();
  // remove the stones moved to the table
  for (Stone stone : stones) {
      if (stone.isOnTable()) {
         clone.remove(stone);
      }
  }
  stones = (ArrayList<Stone>) clone.clone();
  sortStones();
}

2
डाउनवोटर्स को कम से कम डाउनवोट करने से पहले एक टिप्पणी छोड़ देनी चाहिए।
वनवर्ल्ड

2
इस उत्तर के साथ स्वाभाविक रूप से कुछ भी गलत नहीं है, उम्मीद है कि शायद stones = (...) clone.clone();अतिश्योक्तिपूर्ण हो। क्या stones = clone;ऐसा नहीं होगा ?
वाइकिंगस्टेव जूल

मैं सहमत हूं, दूसरा क्लोनिंग अनावश्यक है। आप क्लोन पर पुनरावृत्ति करके इसे सरल बना सकते हैं और सीधे तत्वों को हटा सकते हैं stones। इस तरह आपको cloneचर की भी आवश्यकता नहीं है : for (Stone stone : (ArrayList<Stone>) stones.clone()) {...
Zsolt Sky

0

यदि आपका लक्ष्य सूची से सभी तत्वों को निकालना है, तो आप प्रत्येक आइटम पर पुनरावृत्ति कर सकते हैं, और फिर कॉल कर सकते हैं:

list.clear()

0

मुझे पता है मैं देर से पहुंचता हूं लेकिन मैं इसका उत्तर देता हूं क्योंकि मुझे लगता है कि यह समाधान सरल और सुरुचिपूर्ण है:

List<String> listFixed = new ArrayList<String>();
List<String> dynamicList = new ArrayList<String>();

public void fillingList() {
    listFixed.add("Andrea");
    listFixed.add("Susana");
    listFixed.add("Oscar");
    listFixed.add("Valeria");
    listFixed.add("Kathy");
    listFixed.add("Laura");
    listFixed.add("Ana");
    listFixed.add("Becker");
    listFixed.add("Abraham");
    dynamicList.addAll(listFixed);
}

public void updatingListFixed() {
    for (String newList : dynamicList) {
        if (!listFixed.contains(newList)) {
            listFixed.add(newList);
        }
    }

    //this is for add elements if you want eraser also 

    String removeRegister="";
    for (String fixedList : listFixed) {
        if (!dynamicList.contains(fixedList)) {
            removeResgister = fixedList;
        }
    }
    fixedList.remove(removeRegister);
}

यह सब एक सूची से दूसरी सूची में अपडेट करने के लिए है और आप सभी को केवल एक सूची से बना सकते हैं और विधि अपडेट में आप दोनों सूची की जांच कर सकते हैं और तत्वों की सूची को मिटा या जोड़ सकते हैं। इसका मतलब यह है कि दोनों सूची हमेशा एक ही आकार की होती है


0

Array List की जगह Iterator का प्रयोग करें

एक सेट को टाइप मैच के साथ पुनरावृत्त में परिवर्तित किया जाए

और अगले तत्व पर जाएं और निकालें

Iterator<Insured> itr = insuredSet.iterator();
while (itr.hasNext()) { 
    itr.next();
    itr.remove();
}

अगले को ले जाना यहाँ महत्वपूर्ण है क्योंकि इसे तत्व को हटाने के लिए सूचकांक लेना चाहिए।


0

के बारे में क्या?

import java.util.Collections;

List<A> abc = Collections.synchronizedList(new ArrayList<>());

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