Tại sao tôi không nhận được java.util.ConcienModificationException trong ví dụ này?


176

Lưu ý: Tôi nhận thức được Iterator#remove()phương pháp.

Trong mẫu mã sau đây, tôi không hiểu tại sao phương thức List.removetrong mainném ConcurrentModificationException, nhưng không phải trong removephương thức.

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
Cách an toàn duy nhất để xóa một phần tử khỏi danh sách trong khi lặp qua danh sách đó là sử dụng Iterator#remove(). Tại sao bạn làm theo cách này?
Matt Ball

@MattBall: Tôi chỉ cố gắng xem lý do ở đây có thể là gì. Bởi vì, đó là cùng một "tăng cường cho vòng lặp" trong cả hai phương thức nhưng một phương pháp ném còn phương pháp ConcurrentModificationExceptionkia thì không.
Bhesh Gurung

Có một sự khác biệt trong phần tử bạn loại bỏ. Trong phương thức bạn loại bỏ 'phần tử giữa'. Trong chính bạn loại bỏ cuối cùng. Nếu bạn trao đổi số, bạn sẽ có ngoại lệ trong phương thức của mình. Vẫn không chắc chắn tại sao đó là mặc dù.
Ben van Gompel

Tôi đã gặp một vấn đề tương tự, khi vòng lặp của tôi lặp đi lặp lại một vị trí không tồn tại sau khi tôi xóa một mục trong vòng lặp. Tôi chỉ đơn giản sửa lỗi này bằng cách thêm một return;vòng lặp.
thẳng thắn 17

trên java8 Android, việc loại bỏ phần tử khác với phần tử cuối cùng sẽ gọi ra ConcurrencyModificationException. Vì vậy, đối với trường hợp của bạn, chức năng loại bỏ sẽ có một ngoại lệ ngược lại như bạn quan sát trước đây.
gonglong

Câu trả lời:


262

Đây là lý do: Như đã nói trong Javadoc:

Các trình vòng lặp được trả về bởi các phương thức iterator và listIterator của lớp này là không nhanh: nếu danh sách được sửa đổi về mặt cấu trúc bất cứ lúc nào sau khi trình lặp được tạo ra, theo bất kỳ cách nào ngoại trừ thông qua các phương thức loại bỏ hoặc thêm phương thức của trình lặp, trình lặp đó sẽ đưa ra một phương thức concienModificationException.

Kiểm tra này được thực hiện trong next()phương thức của trình vòng lặp (như bạn có thể thấy bằng stacktrace). Nhưng chúng ta sẽ tiếp cận next()phương thức chỉ khi hasNext()được gửi đúng, đó là cái được gọi bởi mỗi cái để kiểm tra xem ranh giới có được đáp ứng hay không. Trong phương thức remove của bạn, khi hasNext()kiểm tra xem nó có cần trả về một phần tử khác hay không, nó sẽ thấy rằng nó trả về hai phần tử và bây giờ sau khi một phần tử bị xóa, danh sách chỉ chứa hai phần tử. Vì vậy, tất cả là đào và chúng tôi được thực hiện với lặp đi lặp lại. Việc kiểm tra sửa đổi đồng thời không xảy ra, vì điều này được thực hiện trong next()phương thức không bao giờ được gọi.

Tiếp theo chúng ta đến vòng lặp thứ hai. Sau khi chúng tôi xóa số thứ hai, phương thức hasNext sẽ kiểm tra lại nếu có thể trả về nhiều giá trị hơn. Nó đã trả về hai giá trị rồi, nhưng danh sách bây giờ chỉ chứa một. Nhưng mã ở đây là:

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

1! = 2, vì vậy chúng tôi tiếp tục next()phương thức, bây giờ nhận ra rằng ai đó đã gây rối với danh sách và kích hoạt ngoại lệ.

Hy vọng rằng sẽ xóa câu hỏi của bạn lên.

Tóm lược

List.remove()sẽ không ném ConcurrentModificationExceptionkhi nó loại bỏ phần tử cuối cùng thứ hai khỏi danh sách.


5
@pushy: Chỉ trả lời mà dường như trả lời những gì câu hỏi thực sự đang hỏi, và lời giải thích là tốt. Tôi chấp nhận câu trả lời này và cũng +1. Cảm ơn.
Bhesh Gurung

42

Một cách để xử lý nó để loại bỏ thứ gì đó khỏi bản sao của Collection(không phải chính Bộ sưu tập), nếu có. Clonebộ sưu tập ban đầu để tạo một bản sao thông qua a Constructor.

Ngoại lệ này có thể được đưa ra bởi các phương thức đã phát hiện sửa đổi đồng thời của một đối tượng khi sửa đổi đó không được phép.

Đối với trường hợp cụ thể của bạn, trước hết, tôi không nghĩ finallà một cách để xem xét bạn có ý định sửa đổi khai báo danh sách trong quá khứ

private static final List<Integer> integerList;

Cũng xem xét sửa đổi một bản sao thay vì danh sách ban đầu.

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

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

14

Phương thức chuyển tiếp / lặp không hoạt động khi loại bỏ các mục. Bạn có thể xóa phần tử mà không gặp lỗi, nhưng bạn sẽ gặp lỗi thời gian chạy khi bạn cố gắng truy cập các mục đã xóa. Bạn không thể sử dụng trình vòng lặp vì vì Pushy cho thấy nó sẽ gây ra một concExModificationException, do đó, hãy sử dụng một vòng lặp thông thường để thay thế, nhưng lùi lại qua nó.

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

Một giải pháp:

Di chuyển mảng theo thứ tự ngược lại nếu bạn định xóa phần tử danh sách. Đơn giản bằng cách đi ngược qua danh sách bạn tránh truy cập vào một mục đã bị xóa, điều này sẽ loại bỏ ngoại lệ.

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

Ý tưởng tuyệt vời !
dobrivoje

7

Đoạn mã này sẽ luôn đưa ra một ngoại lệ đồng thời.

Quy tắc là "Bạn không được sửa đổi (thêm hoặc xóa các thành phần khỏi danh sách) trong khi lặp qua nó bằng Iterator (xảy ra khi bạn sử dụng vòng lặp for-every)".

JavaDocs:

Các trình vòng lặp được trả về bởi các phương thức iterator và listIterator của lớp này là không nhanh: nếu danh sách được sửa đổi về mặt cấu trúc bất cứ lúc nào sau khi trình lặp được tạo ra, theo bất kỳ cách nào ngoại trừ thông qua các phương thức loại bỏ hoặc thêm phương thức của trình lặp, trình lặp đó sẽ đưa ra một phương thức concienModificationException.

Do đó, nếu bạn muốn sửa đổi danh sách (hoặc bất kỳ bộ sưu tập nào nói chung), hãy sử dụng iterator, vì sau đó nó nhận thức được các sửa đổi và do đó chúng sẽ được xử lý đúng.

Hi vọng điêu nay co ich.


3
OP tuyên bố rõ ràng rằng một trong các vòng lặp KHÔNG ném ngoại lệ và người thẩm vấn là lý do tại sao điều đó xảy ra.
madth3

"'nghi vấn' nghĩa là gì?
Bhushan

4

Tôi đã có vấn đề tương tự nhưng trong trường hợp tôi đã thêm phần tử en vào danh sách lặp. Tôi đã làm nó theo cách này

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

Bây giờ mọi thứ đều ổn vì bạn không tạo bất kỳ trình lặp nào trong danh sách của mình, bạn lặp lại theo cách "thủ công". Và điều kiện i < integerList.size()sẽ không bao giờ đánh lừa bạn bởi vì khi bạn loại bỏ / thêm một cái gì đó vào kích thước Danh sách tăng / giảm Danh sách ..

Hy vọng nó sẽ giúp, cho tôi đó là giải pháp.


Đây không phải là sự thật ! Bằng chứng: chạy đoạn mã này để xem kết quả: public static void main (String ... args) {List <String> listOfBooks = new ArrayList <> (); listOfBooks.add ("Hoàn thành mã"); listOfBooks.add ("Mã 22"); listOfBooks.add ("22 hiệu quả"); listOfBooks.add ("Netbeans 33"); System.err.println ("Trước khi xóa:" + listOfBooks); for (int index = 0; index <listOfBooks.size (); index ++) {if (listOfBooks.get (index) .contains ("22")) {listOfBooks.remove (index); }} System.err.println ("Sau khi xóa:" + listOfBooks); }
dobrivoje

1

Nếu bạn sử dụng các bộ sưu tập copy-on-write, nó sẽ hoạt động; tuy nhiên khi bạn sử dụng list.iterator (), Iterator được trả về sẽ luôn tham chiếu bộ sưu tập các phần tử như khi (như bên dưới) list.iterator () được gọi, ngay cả khi một luồng khác sửa đổi bộ sưu tập. Bất kỳ phương thức đột biến nào được gọi trên Iterator hoặc ListIterator dựa trên copy-on-write (như add, set hoặc remove) sẽ ném UnceptionedOperationException.

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

Điều này chạy tốt trên Java 1.6

~% javac RemoveListEuityDemo.java
~% java RemoveListEuityDemo
~% cat RemoveListEuityDemo.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);
            }
        }
    }
}

~%


Xin lỗi về lỗi đánh máy Điều này 'chạy' tốt trên Java 1.6
battosai

Hmm ... Có thể bạn có một triển khai khác. Nhưng theo đặc điểm kỹ thuật, nó được cho là để làm điều đó, IMO. Nhìn vào câu trả lời của @ Pushy.
Bhesh Gurung

thật không may, id không có trên java 1.8
dobrivoje

0

Trong trường hợp của tôi, tôi đã làm nó như thế này:

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

0

Thay đổi Iterator for eachthành for loopđể giải quyết.

Và lý do là:

Các trình vòng lặp được trả về bởi các phương thức iterator và listIterator của lớp này là không nhanh: nếu danh sách được sửa đổi về mặt cấu trúc bất cứ lúc nào sau khi trình lặp được tạo ra, theo bất kỳ cách nào ngoại trừ thông qua các phương thức loại bỏ hoặc thêm phương thức của trình lặp, trình lặp đó sẽ đưa ra một phương thức concienModificationException.

- Tài liệu Java được giới thiệu.


-1

Kiểm tra người đàn ông mã của bạn ....

Trong phương thức chính, bạn đang cố gắng loại bỏ phần tử thứ 4 không có và do đó xảy ra lỗi. Trong phương thức remove () bạn đang cố gắng loại bỏ phần tử thứ 3 có ở đó và do đó không có lỗi.


Bạn đã nhầm: các số 23không phải là chỉ số cho danh sách, mà là các phần tử. Cả hai logic loại bỏ kiểm tra đối equalsvới các phần tử danh sách, không phải chỉ mục của các phần tử. Hơn nữa, nếu nó liên quan đến chỉ số, thì nó sẽ IndexOutOfBoundsExceptionkhông ConcurrentModificationException.
Malte Hartwig
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.