ทำไมฉันไม่ได้รับ java.util.ConcurrentModificationException ในตัวอย่างนี้


176

หมายเหตุ: ฉันตระหนักถึงIterator#remove()วิธีการ

ในตัวอย่างโค้ดต่อไปนี้ฉันไม่เข้าใจว่าทำไมList.removein in mainthrows ConcurrentModificationExceptionแต่ไม่ได้อยู่ในremovemethod

public class RemoveListElementDemo {    
    private static final List<Integer> integerList;

    static {
        integerList = new ArrayList<Integer>();
        integerList.add(1);
        integerList.add(2);
        integerList.add(3);
    }

    public static void remove(Integer toRemove) {
        for(Integer integer : integerList) {
            if(integer.equals(toRemove)) {                
                integerList.remove(integer);
            }
        }
    }

    public static void main(String... args) {                
        remove(Integer.valueOf(2));

        Integer toRemove = Integer.valueOf(3);
        for(Integer integer : integerList) {
            if(integer.equals(toRemove)) {                
                integerList.remove(integer);
            }
        }
    }
}

3
เพียงวิธีที่ปลอดภัยในการลบองค์ประกอบจากรายการขณะ iterating Iterator#remove()กว่ารายการที่มีการใช้งาน ทำไมคุณถึงทำแบบนี้?
Matt Ball

@ MattBall: ฉันแค่พยายามที่จะดูว่าเหตุผลที่นี่อาจจะเป็น เพราะมันเป็นแบบเดียวกัน "ปรับปรุงสำหรับการวนซ้ำ" ในทั้งสองวิธี แต่อย่างใดอย่างหนึ่งโยนConcurrentModificationExceptionและอื่น ๆ ไม่ได้
Bhesh Gurung

มีความแตกต่างในองค์ประกอบที่คุณลบในวิธีการลบ 'องค์ประกอบกลาง' ของคุณ ในหลักคุณจะลบล่าสุด หากคุณสลับหมายเลขที่คุณได้รับข้อยกเว้นในวิธีการของคุณ ยังไม่แน่ใจว่าทำไมถึงเป็นเช่นนั้น
Ben van Gompel

ฉันมีปัญหาที่คล้ายกันเมื่อลูปของฉันวนซ้ำตำแหน่งที่ไม่มีอยู่หลังจากฉันลบรายการในลูป ฉันเพียงแค่แก้ไขสิ่งนี้ด้วยการเพิ่มreturn;วงเข้าไป
frank17

บน java8 Android การลบองค์ประกอบอื่นที่ไม่ใช่องค์ประกอบสุดท้ายจะเรียกใช้ ConcurrentModificationException ดังนั้นสำหรับกรณีของคุณฟังก์ชั่นลบจะได้รับการยกเว้นซึ่งตรงข้ามกับที่คุณสังเกตเห็นมาก่อน
gonglong

คำตอบ:


262

นี่คือเหตุผล: ตามที่กล่าวไว้ใน Javadoc:

ตัววนซ้ำที่ส่งคืนโดยตัววนซ้ำของคลาสนี้และวิธี listIterator นั้นล้มเหลวอย่างรวดเร็ว: หากรายการนั้นได้รับการแก้ไขเชิงโครงสร้างในเวลาใด ๆ หลังจากที่ตัวสร้างตัววนซ้ำถูกสร้างขึ้นด้วยวิธีใด ๆ ยกเว้นผ่านการลบหรือเพิ่มวิธีการ

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

ต่อไปเราไปที่วงที่สอง หลังจากเราลบหมายเลขที่สองแล้ววิธี hasNext จะตรวจสอบอีกครั้งหากสามารถคืนค่าได้มากขึ้น มันคืนค่าสองค่าไปแล้ว แต่ตอนนี้รายการมีเพียงค่าเดียว แต่รหัสที่นี่คือ:

public boolean hasNext() {
        return cursor != size();
}

1! = 2 ดังนั้นเราจึงดำเนินการตามnext()วิธีต่อไปซึ่งตอนนี้ตระหนักดีว่ามีใครบางคนยุ่งกับรายการและยิงข้อยกเว้น

หวังว่าจะล้างคำถามของคุณ

สรุป

List.remove()จะไม่โยนConcurrentModificationExceptionเมื่อมันลบองค์ประกอบสุดท้ายที่สองจากรายการ


5
@pushy: เพียงคำตอบที่ดูเหมือนจะตอบคำถามที่ถามจริงและคำอธิบายนั้นดี ฉันยอมรับคำตอบนี้และ +1 ด้วย ขอบคุณ
Bhesh Gurung

42

วิธีหนึ่งในการจัดการกับมันเพื่อลบบางสิ่งออกจากสำเนาของCollection(ไม่ใช่ Collection เอง) หากทำได้ คอลเลกชันเดิมมันทำสำเนาผ่านได้CloneConstructor

ข้อยกเว้นนี้อาจถูกส่งออกไปโดยวิธีการที่ตรวจพบการแก้ไขพร้อมกันของวัตถุเมื่อการแก้ไขดังกล่าวไม่ได้รับอนุญาต

สำหรับกรณีเฉพาะของคุณก่อนอื่นฉันไม่คิดว่าfinalเป็นวิธีที่จะไปพิจารณาว่าคุณตั้งใจจะแก้ไขรายการประกาศที่ผ่านมา

private static final List<Integer> integerList;

ลองพิจารณาแก้ไขสำเนาแทนรายการต้นฉบับ

List<Integer> copy = new ArrayList<Integer>(integerList);

for(Integer integer : integerList) {
    if(integer.equals(remove)) {                
        copy.remove(integer);
    }
}

14

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

List<Integer> integerList;
integerList = new ArrayList<Integer>();
integerList.add(1);
integerList.add(2);
integerList.add(3);

int size= integerList.size();

//Item to remove
Integer remove = Integer.valueOf(3);

ทางออก:

สำรวจอาร์เรย์ตามลำดับย้อนกลับหากคุณกำลังจะลบองค์ประกอบรายการ เพียงแค่ย้อนกลับไปยังรายการที่คุณหลีกเลี่ยงการเยี่ยมชมรายการที่ถูกลบออกซึ่งจะเป็นการยกเว้น

//To remove items from the list, start from the end and go backwards through the arrayList
//This way if we remove one from the beginning as we go through, then we will avoid getting a runtime error
//for java.lang.IndexOutOfBoundsException or java.util.ConcurrentModificationException as when we used the iterator
for (int i=size-1; i> -1; i--) {
    if (integerList.get(i).equals(remove) ) {
        integerList.remove(i);
    }
}

ความคิดหลักแหลม !
dobrivoje

7

ข้อมูลโค้ดนี้จะมักจะโยน ConcurrentModificationException

กฎคือ "คุณไม่สามารถแก้ไข (เพิ่มหรือลบองค์ประกอบออกจากรายการ) ในขณะที่วนซ้ำโดยใช้ Iterator (ซึ่งจะเกิดขึ้นเมื่อคุณใช้การวนรอบแต่ละครั้ง)"

JavaDocs:

ตัววนซ้ำที่ส่งคืนโดยตัววนซ้ำของคลาสนี้และวิธี listIterator นั้นล้มเหลวอย่างรวดเร็ว: หากรายการนั้นได้รับการแก้ไขเชิงโครงสร้างในเวลาใด ๆ หลังจากที่ตัวสร้างตัววนซ้ำถูกสร้างขึ้นด้วยวิธีใด ๆ ยกเว้นผ่านการลบหรือเพิ่มวิธีการ

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

หวังว่านี่จะช่วยได้


3
OP ชัดเจนระบุว่าหนึ่งในลูปไม่ได้ทำให้เกิดข้อยกเว้นและอวดดีเป็นสาเหตุที่เกิดขึ้น
madth3

คุณหมายถึง 'interrogant' หมายถึงอะไร
ภูชัน

4

ฉันมีปัญหาเดียวกันนั้น แต่ในกรณีที่ฉันกำลังเพิ่มองค์ประกอบ en ลงในรายการซ้ำแล้วซ้ำอีก ฉันทำอย่างนี้

public static void remove(Integer remove) {
    for(int i=0; i<integerList.size(); i++) {
        //here is maybe fine to deal with integerList.get(i)==null
        if(integerList.get(i).equals(remove)) {                
            integerList.remove(i);
        }
    }
}

ตอนนี้ทุกอย่างไปได้ดีเพราะคุณไม่ได้สร้างตัววนซ้ำในรายการของคุณ และสภาพi < integerList.size()จะไม่หลอกคุณเพราะเมื่อคุณลบ / เพิ่มบางสิ่งบางอย่างลงในขนาดรายการของการลด / เพิ่มรายชื่อ ..

หวังว่ามันจะช่วยได้สำหรับฉันนั่นเป็นวิธีแก้ปัญหา


นี่ไม่เป็นความจริง ! หลักฐาน: เรียกใช้ส่วนย่อยนี้เพื่อดูผลลัพธ์: public void main main (String ... args) {List <String> listOfBooks = new ArrayList <> (); listOfBooks.add ("การเติมโค้ด"); listOfBooks.add ("รหัส 22"); listOfBooks.add ("22 มีผลบังคับใช้"); listOfBooks.add ("Netbeans 33"); System.err.println ("ก่อนที่จะลบ:" + listOfBooks); สำหรับ (int index = 0; index <listOfBooks.size (); index ++) {ถ้า (listOfBooks.get (index) .contain ("22")) {listOfBooks.remove (index); }} System.err.println ("หลังจากลบ:" + listOfBooks); }
dobrivoje

1

หากคุณใช้คอลเลกชันคัดลอกเมื่อเขียนมันจะทำงาน; อย่างไรก็ตามเมื่อคุณใช้ list.iterator () Iterator ที่ส่งคืนจะอ้างอิงคอลเลกชันขององค์ประกอบเหมือนเดิมทุกครั้งที่เรียกว่า list.iterator (ตามด้านล่าง) แม้ว่าจะมีเธรดอื่นที่ปรับเปลี่ยนคอลเลกชันก็ตาม วิธีการกลายพันธุ์ใด ๆ ที่เรียกใช้บน Iterator ที่ใช้การคัดลอกเมื่อเขียนหรือ ListIterator (เช่นเพิ่มตั้งค่าหรือลบ) จะโยน UnsupportedOperationException

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

public class RemoveListElementDemo {    
    private static final List<Integer> integerList;

    static {
        integerList = new CopyOnWriteArrayList<>();
        integerList.add(1);
        integerList.add(2);
        integerList.add(3);
    }

    public static void remove(Integer remove) {
        for(Integer integer : integerList) {
            if(integer.equals(remove)) {                
                integerList.remove(integer);
            }
        }
    }

    public static void main(String... args) {                
        remove(Integer.valueOf(2));

        Integer remove = Integer.valueOf(3);
        for(Integer integer : integerList) {
            if(integer.equals(remove)) {                
                integerList.remove(integer);
            }
        }
    }
}

0

สิ่งนี้ทำงานได้ดีบน Java 1.6

~% javac RemoveListElementDemo.java
~% java RemoveListElementDemo
~% cat RemoveListElementDemo.java

import java.util.*;
public class RemoveListElementDemo {    
    private static final List<Integer> integerList;

    static {
        integerList = new ArrayList<Integer>();
        integerList.add(1);
        integerList.add(2);
        integerList.add(3);
    }

    public static void remove(Integer remove) {
        for(Integer integer : integerList) {
            if(integer.equals(remove)) {                
                integerList.remove(integer);
            }
        }
    }

    public static void main(String... args) {                
        remove(Integer.valueOf(2));

        Integer remove = Integer.valueOf(3);
        for(Integer integer : integerList) {
            if(integer.equals(remove)) {                
                integerList.remove(integer);
            }
        }
    }
}

~%


ขออภัยเกี่ยวกับการพิมพ์ผิด 'ทำงาน' ได้ดีบน Java 1.6
battosai

อืม ... อาจเป็นเพราะคุณมีการนำไปใช้ที่แตกต่างออกไป แต่ตามสเปคมันควรจะทำอย่างนั้น IMO ดูคำตอบของ @ Pushy
Bhesh Gurung

น่าเสียดายที่ id ไม่ได้อยู่บน java 1.8
dobrivoje

0

ในกรณีของฉันฉันทำแบบนี้:

int cursor = 0;
do {
    if (integer.equals(remove))
        integerList.remove(cursor);
    else cursor++;
} while (cursor != integerList.size());

0

เปลี่ยน Iterator for eachเป็นวิธีfor loopแก้ปัญหา

และเหตุผลก็คือ:

ตัววนซ้ำที่ส่งคืนโดยตัววนซ้ำของคลาสนี้และวิธี listIterator นั้นล้มเหลวอย่างรวดเร็ว: หากรายการนั้นได้รับการแก้ไขเชิงโครงสร้างในเวลาใด ๆ หลังจากที่ตัวสร้างตัววนซ้ำถูกสร้างขึ้นด้วยวิธีใด ๆ ยกเว้นผ่านการลบหรือเพิ่มวิธีการ

- เอกสาร Java อ้างอิง


-1

ตรวจสอบรหัสคนของคุณ ....

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


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