วิธีหลีกเลี่ยง java.util.ConcurrentModificationException เมื่อวนซ้ำผ่านและลบองค์ประกอบออกจาก ArrayList


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: มันเป็นเรื่องยากที่จะตอบโดยไม่เห็นว่าโค้ดสองบิตโต้ตอบกันอย่างไร โดยทั่วไปคุณไม่สามารถทำเช่นนั้นได้ ไม่ชัดเจนว่าการโคลนรายการจะช่วยได้หรือไม่ คุณสามารถให้รายละเอียดเพิ่มเติมในคำถามของคุณ?
Jon Skeet

ฉันรู้ว่าการโคลนรายการจะช่วยได้ แต่ฉันไม่รู้ว่านี่เป็นวิธีการที่ดีหรือไม่ แต่ฉันจะเพิ่มรหัสเพิ่มเติม
RoflcoptrException

2
การแก้ปัญหานี้ยังนำไปสู่การ java.util.ConcurrentModificationException ดูstackoverflow.com/a/18448699/2914140
CoolMind

1
@CoolMind: หากไม่มีหลายเธรดรหัสนี้ควรใช้ได้
Jon Skeet

17

จาก JavaDocs ของ ArrayList

iterators ที่ส่งกลับโดย iterator และ ListIterator วิธีการของชั้นนี้จะล้มเหลวอย่างรวดเร็ว: ถ้ารายการมีการปรับเปลี่ยนโครงสร้างในเวลาใด ๆ หลังจาก iterator จะถูกสร้างขึ้นในทางใด ๆ ยกเว้นผ่าน iterator ของลบเองหรือเพิ่มวิธีการที่ iterator จะโยน ConcurrentModificationException


6
และคำตอบของคำถามอยู่ที่ไหน
Adelin

อย่างที่มันบอกยกเว้นผ่านวิธีลบหรือเพิ่มของ iterator เอง
Varun Achar

14

คุณกำลังพยายามลบค่าออกจากรายการในขั้นสูง "for loop" ซึ่งเป็นไปไม่ได้แม้ว่าคุณจะใช้เคล็ดลับใด ๆ (ซึ่งคุณทำในรหัสของคุณ) วิธีที่ดีกว่าคือการกำหนดระดับตัววนซ้ำตามที่คนอื่น ๆ แนะนำที่นี่

ฉันสงสัยว่าผู้คนไม่ได้แนะนำวิธีการวนซ้ำแบบดั้งเดิม

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

ฉันคิดว่าคำตอบนี้ได้รับการแก้ไขเพื่อแก้ไขปัญหาในความคิดเห็นข้างต้นดังนั้นขณะนี้มันทำงานได้ดีอย่างน้อยสำหรับฉัน
Kira Resari

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

ฉันมีชื่อข้างต้นของรายการ Array และฉันต้องการลบชื่อ "def" ออกจากรายการด้านบน

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

รหัสข้างต้นจะพ่นข้อยกเว้นConcurrentModificationExceptionเนื่องจากคุณกำลังแก้ไขรายการในขณะที่วนซ้ำ

ดังนั้นหากต้องการลบชื่อ "def" ออกจาก Arraylist ด้วยวิธีนี้

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

รหัสข้างต้นผ่าน iterator เราสามารถลบชื่อ "def" ออกจาก Arraylist และลองพิมพ์อาร์เรย์คุณจะเห็นผลลัพธ์ด้านล่าง

เอาต์พุต: [abc, ghi, xyz]


ที่อื่นเราสามารถใช้รายการที่เกิดขึ้นพร้อมกันซึ่งมีอยู่ในแพ็คเกจที่เกิดขึ้นพร้อมกันเพื่อให้คุณสามารถทำการลบและเพิ่มการดำเนินการในขณะที่ทำซ้ำ ตัวอย่างเช่นดูข้อมูลโค้ดด้านล่าง ArrayList <String> names = new ArrayList <String> (); CopyOnWriteArrayList <String> copyNames = new CopyOnWriteArrayList <String> (ชื่อ); สำหรับ (ชื่อสตริง: copyNames) {if (name.equals ("def")) {copyNames.remove ("def"); }}
พระอินทร์ K

CopyOnWriteArrayList จะเป็นการดำเนินการที่ประหยัดที่สุด
พระอินทร์ K

5

ทางเลือกหนึ่งคือการปรับเปลี่ยนremoveAวิธีการนี้ -

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

แต่นี่หมายความว่าคุณdoSomething()ควรจะสามารถส่งผ่านiteratorไปยังremoveวิธีการได้ ไม่ใช่ความคิดที่ดีมาก

คุณสามารถทำได้สองวิธีการขั้นตอน: ในวงแรกเมื่อคุณย้ำผ่านรายการแทนการลบองค์ประกอบที่เลือกทำเครื่องหมายพวกเขาเป็นที่จะถูกลบ สำหรับสิ่งนี้คุณสามารถคัดลอกองค์ประกอบเหล่านี้ (สำเนาตื้น) ไปยังอีกองค์ประกอบหนึ่งได้Listได้

จากนั้นเมื่อทำซ้ำของคุณเสร็จแล้วเพียงทำremoveAllองค์ประกอบแรกจากรายการทั้งหมดในรายการที่สอง


ยอดเยี่ยมฉันใช้วิธีการเดียวกันแม้ว่าฉันวนซ้ำสองครั้ง มันทำให้สิ่งต่าง ๆ ง่ายและไม่มีปัญหาเกิดขึ้นพร้อมกัน :)
464 Pankaj Nimgade เมื่อ

1
ฉันไม่เห็นว่า Iterator มีเมธอด remove (a) การลบ () จะไม่มีการโต้แย้งdocs.oracle.com/javase/8/docs/api/java/util/Iterator.htmlสิ่งที่ฉันขาดหายไปคืออะไร
c0der

5

นี่คือตัวอย่างที่ฉันใช้รายการที่แตกต่างกันเพื่อเพิ่มวัตถุสำหรับการลบจากนั้นฉันใช้กระแสข้อมูลก่อนที่จะลบองค์ประกอบออกจากรายการเดิม:

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

ฉันคิดว่าคุณกำลังทำงานพิเศษที่ดำเนินการสองลูปในกรณีที่แย่ที่สุดลูปจะเป็นของรายการทั้งหมด จะทำได้ง่ายและราคาไม่แพงในหนึ่งวงเท่านั้น
Luis Carlos

ฉันไม่คิดว่าคุณสามารถลบวัตถุออกจากภายในลูปแรกได้ดังนั้นความจำเป็นในการลบลูปพิเศษอีกทั้งการลบลูปเป็นเพียงวัตถุสำหรับการลบ - บางทีคุณสามารถเขียนตัวอย่างด้วยลูปเพียงอันเดียวฉันอยากเห็น - ขอบคุณ @ LuisCarlos
serup

อย่างที่คุณพูดด้วยรหัสนี้คุณจะไม่สามารถลบองค์ประกอบใด ๆ ใน for-loop ได้เพราะมันทำให้เกิดข้อยกเว้น java.util.ConcurrentModificationException อย่างไรก็ตามคุณสามารถใช้พื้นฐานสำหรับ ที่นี่ฉันเขียนตัวอย่างโดยใช้ส่วนหนึ่งของรหัสของคุณ
Luis Carlos

1
สำหรับ (int i = 0; i <customersTableViewItems.size (); i ++) {diff = currentTimestamp.getValue (). getTime () - customersTableViewItems.get (i) .timestamp.getValue (). diffSeconds = diff / 1000% 60; if (diffSeconds> 10) {customersTableViewItems.remove (i--); }} มีความสำคัญฉัน - เพราะคุณไม่ต้องการข้าม elment ใด ๆ นอกจากนี้คุณสามารถใช้เมธอด removeIf (ตัวกรอง <? super E> ตัวกรอง) ที่จัดเตรียมโดยคลาส ArrayList หวังว่าความช่วยเหลือนี้
Luis Carlos

1
ข้อยกเว้นเกิดขึ้นเนื่องจากใน for-loop มีเป็นการอ้างอิงที่ใช้งานกับตัววนซ้ำของรายการ ตามปกติแล้วไม่มีการอ้างอิงและคุณมีความยืดหยุ่นในการเปลี่ยนแปลงข้อมูลมากขึ้น หวังว่าความช่วยเหลือนี้
Luis Carlos

3

แทนที่จะใช้สำหรับแต่ละลูปให้ใช้ปกติสำหรับลูป ตัวอย่างเช่นรหัสด้านล่างลบองค์ประกอบทั้งหมดในรายการอาร์เรย์โดยไม่ต้องให้ java.util.ConcurrentModificationException คุณสามารถแก้ไขเงื่อนไขในลูปตามกรณีการใช้งานของคุณ

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

          e.remove(i);
        }


2

โซลูชัน Java 8 ทางเลือกโดยใช้สตรีม:

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

ใน Java 7 คุณสามารถใช้ Guava แทน:

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

โปรดทราบว่าตัวอย่าง Guava ให้ผลลัพธ์ในรายการที่ไม่เปลี่ยนรูปซึ่งอาจจะใช่หรือไม่ใช่ก็ได้ตามที่คุณต้องการ


1

คุณยังสามารถใช้ CopyOnWriteArrayList แทน ArrayList นี่เป็นวิธีการที่แนะนำล่าสุดโดยจาก 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

"ฉันควรโคลนรายการก่อนหรือไม่"

นั่นจะเป็นทางออกที่ง่ายที่สุดลบออกจากโคลนและคัดลอกโคลนกลับหลังจากลบ

ตัวอย่างจากเกม rummikub ของฉัน:

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
อย่างน้อย Downvoters ควรแสดงความคิดเห็นก่อน downvoting
OneWorld

2
ไม่มีอะไรผิดปกติกับคำตอบนี้คาดว่าอาจstones = (...) clone.clone();จะฟุ่มเฟือย จะไม่stones = clone;ทำเช่นเดียวกัน?
vikingsteve

ฉันยอมรับการโคลนครั้งที่สองนั้นไม่จำเป็น 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

ใช้ Iterator แทน Array List

ตั้งค่าให้แปลงเป็นตัววนซ้ำด้วยการจับคู่ประเภท

และย้ายไปองค์ประกอบถัดไปและลบ

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

การย้ายไปยังสิ่งต่อไปเป็นสิ่งสำคัญที่นี่เนื่องจากควรใช้ดัชนีเพื่อลบองค์ประกอบ



โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.