Cách tránh java.util.ConcienModificationException khi lặp qua và xóa các phần tử khỏi ArrayList


203

Tôi có một ArrayList mà tôi muốn lặp lại. Trong khi lặp lại nó, tôi phải loại bỏ các yếu tố cùng một lúc. Rõ ràng điều này ném a java.util.ConcurrentModificationException.

Thực hành tốt nhất để xử lý vấn đề này là gì? Tôi có nên nhân bản danh sách đầu tiên?

Tôi loại bỏ các phần tử không phải trong chính vòng lặp mà là một phần khác của mã.

Mã của tôi trông như thế này:

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.doSomethingcó thể gọi Test.removeA();


Câu trả lời:


325

Hai lựa chọn:

  • Tạo một danh sách các giá trị bạn muốn xóa, thêm vào danh sách đó trong vòng lặp, sau đó gọi originalList.removeAll(valuesToRemove)ở cuối
  • Sử dụng remove()phương thức trên chính trình vòng lặp. Lưu ý rằng điều này có nghĩa là bạn không thể sử dụng vòng lặp nâng cao.

Như một ví dụ về tùy chọn thứ hai, loại bỏ bất kỳ chuỗi nào có độ dài lớn hơn 5 khỏi danh sách:

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
Tôi nên đã đề cập rằng tôi loại bỏ các phần tử trong một phần khác của mã chứ không phải chính vòng lặp.
RoflcoptrException

@Roflcoptr: Thật khó để trả lời mà không thấy hai bit mã tương tác như thế nào. Về cơ bản, bạn không thể làm điều đó. Không rõ liệu việc nhân bản danh sách trước có giúp ích gì không mà không thấy tất cả được kết hợp với nhau như thế nào. Bạn có thể cho biết thêm chi tiết trong câu hỏi của bạn?
Jon Skeet

Tôi biết rằng nhân bản danh sách sẽ giúp ích, nhưng tôi không biết liệu đó có phải là một cách tiếp cận tốt hay không. Nhưng tôi sẽ thêm một số mã.
RoflcoptrException

2
Giải pháp này cũng dẫn đến java.util.ConcienModificationException, xem stackoverflow.com/a/18448699/2914140 .
CoolMind

1
@CoolMind: Không có nhiều luồng, mã này sẽ ổn.
Jon Skeet

17

Từ JavaDocs của ArrayList

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 iterator, thì iterator sẽ ném ra một concuxModificationException.


6
và câu trả lời cho câu hỏi ở đâu?
Adelin

Giống như đã nói, ngoại trừ thông qua các phương thức xóa hoặc thêm riêng của trình lặp
Varun Achar

14

Bạn đang cố gắng xóa giá trị khỏi danh sách trong "vòng lặp" nâng cao, điều này là không thể, ngay cả khi bạn áp dụng bất kỳ mẹo nào (mà bạn đã làm trong mã của mình). Cách tốt hơn là mã cấp độ lặp như lời khuyên khác ở đây.

Tôi tự hỏi làm thế nào mọi người không đề xuất cách tiếp cận truyền thống.

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

Điều này làm việc như là tốt.


2
Điều này LAF không đúng!!! Khi bạn loại bỏ một phần tử, phần tiếp theo sẽ lấy vị trí của nó và trong khi tôi tăng phần tử tiếp theo sẽ không được kiểm tra trong lần lặp tiếp theo. Trong trường hợp này, bạn nên truy cập (int i = lStringList.size (); i> -1;
i--

1
Đồng ý! Thay thế là để thực hiện i--; trong nếu điều kiện trong vòng lặp for.
suhas0sn07

Tôi nghĩ rằng câu trả lời này đã được chỉnh sửa để giải quyết các vấn đề trong các ý kiến ​​trên, vì vậy bây giờ nó hoạt động tốt, ít nhất là đối với tôi.
Kira Resari

11

Bạn thực sự chỉ nên lặp lại mảng theo cách truyền thống

Mỗi khi bạn loại bỏ một yếu tố khỏi danh sách, các yếu tố sau sẽ được đẩy về phía trước. Miễn là bạn không thay đổi các phần tử khác với phần tử lặp, đoạn mã sau sẽ hoạt động.

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

Trong Java 8, bạn có thể sử dụng Giao diện Bộ sưu tập và thực hiện việc này bằng cách gọi phương thức remove If:

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

Thêm thông tin có thể được tìm thấy ở đây


6

Thực hiện các vòng lặp theo cách thông thường, java.util.ConcurrentModificationException là một lỗi liên quan đến các yếu tố được truy cập.

Hãy thử

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

Bạn tránh được java.util.ConcurrentModificationExceptionbằng cách không xóa bất cứ thứ gì khỏi danh sách. Khó khăn. :) Bạn thực sự không thể gọi đây là "cách thông thường" để lặp lại một danh sách.
Bầu trời Zsolt

6

Trong khi lặp lại danh sách, nếu bạn muốn loại bỏ phần tử là có thể. Hãy xem ví dụ dưới đây của tôi,

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

Tôi có tên trên của danh sách Array. Và tôi muốn xóa tên "def" khỏi danh sách trên,

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

Đoạn mã trên đưa ra ngoại lệ ConcurrencyModificationException vì bạn đang sửa đổi danh sách trong khi lặp.

Vì vậy, để xóa tên "def" khỏi Arraylist bằng cách này,

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

Đoạn mã trên, thông qua iterator, chúng ta có thể xóa tên "def" khỏi Arraylist và thử in mảng, bạn sẽ thấy đầu ra bên dưới.

Đầu ra: [abc, ghi, xyz]


Khác, chúng ta có thể sử dụng danh sách đồng thời có sẵn trong gói đồng thời, để bạn có thể thực hiện loại bỏ và thêm các hoạt động trong khi lặp. Ví dụ, xem đoạn mã dưới đây. ArrayList <String> name = new ArrayList <String> (); CopyOnWriteArrayList <String> copyNames = new CopyOnWriteArrayList <String> (tên); for (Tên chuỗi: copyNames) {if (name.equals ("def")) {copyNames.remove ("def"); }}
Indra K

CopyOnWriteArrayList sẽ là hoạt động tốn kém nhất.
Indra K

5

Một lựa chọn là sửa đổi removeAphương thức này -

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

Nhưng điều này có nghĩa là bạn doSomething()sẽ có thể vượt qua iteratorđể các removephương pháp. Không phải là một ý tưởng rất tốt.

Bạn có thể làm điều này theo cách tiếp cận hai bước: Trong vòng lặp đầu tiên khi bạn lặp qua danh sách, thay vì xóa các thành phần đã chọn, hãy đánh dấu chúng là sẽ bị xóa . Đối với điều này, bạn có thể chỉ cần sao chép các yếu tố này (bản sao nông) sang một yếu tố khác List.

Sau đó, khi việc lặp lại của bạn được thực hiện, chỉ cần thực hiện một removeAlltừ danh sách đầu tiên tất cả các thành phần trong danh sách thứ hai.


Tuyệt vời, tôi đã sử dụng cùng một cách tiếp cận, mặc dù tôi lặp hai lần. nó làm cho mọi thứ trở nên đơn giản và không có vấn đề đồng thời với nó :)
Pankaj Nimgade

1
Tôi không thấy rằng Iterator có phương thức remove (a). Việc xóa () không có đối số docs.oracle.com/javase/8/docs/api/java/util/Iterator.html tôi đang thiếu gì?
c0der

5

Dưới đây là một ví dụ trong đó tôi sử dụng một danh sách khác để thêm các đối tượng để xóa, sau đó tôi sử dụng stream.foreach để xóa các thành phần khỏi danh sách ban đầu:

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

Tôi nghĩ rằng bạn đang làm thêm việc thực hiện hai vòng lặp, trong trường hợp xấu nhất là các vòng lặp sẽ thuộc toàn bộ danh sách. Sẽ đơn giản nhất và ít tốn kém hơn khi thực hiện nó chỉ trong một vòng lặp.
Luis Carlos

Tôi không nghĩ bạn có thể xóa đối tượng từ trong vòng lặp đầu tiên, do đó cần thêm vòng loại bỏ, vòng lặp loại bỏ chỉ là đối tượng để xóa - có lẽ bạn có thể viết một ví dụ chỉ với một vòng lặp, tôi muốn xem nó - cảm ơn @ LuisCarlos
serup

Như bạn nói với mã này, bạn không thể xóa bất kỳ phần tử nào trong vòng lặp for vì nó gây ra ngoại lệ java.util.ConcienModificationException. Tuy nhiên, bạn có thể sử dụng một cơ bản cho. Ở đây tôi viết một ví dụ sử dụng một phần mã của bạn.
Luis Carlos

1
for (int i = 0; i <fansTableViewItems.size (); i ++) {diff = currentTimestamp.getValue (). getTime () - fansTableViewItems.get (i) .timestamp.getValue (). getTime (); diffSeconds = diff / 1000% 60; if (diffSeconds> 10) {fansTableViewItems.remove (i--); }} Điều quan trọng là tôi-- bởi vì bạn không muốn bỏ qua bất kỳ cuộc bầu cử nào. Ngoài ra, bạn có thể sử dụng phương thức remove If (Bộ lọc dự đoán <? Super E>) được cung cấp bởi lớp ArrayList. Hy vọng sự giúp đỡ này
Luis Carlos

1
Ngoại lệ xảy ra bởi vì trong vòng lặp for có tham chiếu hoạt động đến iterator của danh sách. Thông thường, không có tài liệu tham khảo và bạn có thể linh hoạt hơn để thay đổi dữ liệu. Hy vọng sự giúp đỡ này
Luis Carlos

3

Thay vì sử dụng Đối với mỗi vòng lặp, hãy sử dụng vòng lặp bình thường. ví dụ, đoạn mã dưới đây sẽ loại bỏ tất cả phần tử trong danh sách mảng mà không đưa ra java.util.ConcienModificationException. Bạn có thể sửa đổi điều kiện trong vòng lặp theo trường hợp sử dụng của bạn.

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

          e.remove(i);
        }

2

Làm một số điều đơn giản như thế này:

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

2

Một giải pháp Java 8 thay thế bằng cách sử dụng luồng:

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

Trong Java 7, bạn có thể sử dụng Guava thay thế:

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

Lưu ý, ví dụ về quả ổi dẫn đến một danh sách bất biến có thể có hoặc không phải là thứ bạn muốn.


1

Bạn cũng có thể sử dụng CopyOnWriteArrayList thay vì ArrayList. Đây là cách tiếp cận được đề xuất mới nhất từ ​​JDK 1.5 trở đi.


1

Trong trường hợp của tôi, câu trả lời được chấp nhận không hoạt động, Nó dừng Ngoại lệ nhưng nó gây ra một số mâu thuẫn trong Danh sách của tôi. Các giải pháp sau đây là hoàn hảo làm việc cho tôi.

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

Trong mã này, tôi đã thêm các mục để loại bỏ, trong một danh sách khác và sau đó sử dụng list.removeAllphương thức để loại bỏ tất cả các mục cần thiết.


0

"Tôi có nên nhân bản danh sách đầu tiên?"

Đó sẽ là giải pháp đơn giản nhất, xóa khỏi bản sao và sao chép lại bản sao sau khi xóa.

Một ví dụ từ trò chơi rummikub của tôi:

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 ít nhất nên để lại một bình luận trước khi downvot.
OneWorld

2
Không có gì sai với câu trả lời này mong đợi có lẽ đó stones = (...) clone.clone();là thừa. Sẽ không stones = clone;làm như vậy?
vikingsteve

Tôi đồng ý, nhân bản thứ hai là không cần thiết. Bạn có thể đơn giản hóa điều này bằng cách lặp lại trên bản sao và loại bỏ các yếu tố trực tiếp khỏi stones. Bằng cách này, bạn thậm chí không cần clonebiến: for (Stone stone : (ArrayList<Stone>) stones.clone()) {...
Zsolt Sky

0

Nếu mục tiêu của bạn là xóa tất cả các thành phần khỏi danh sách, bạn có thể lặp lại qua từng mục và sau đó gọi:

list.clear()

0

Tôi đến muộn tôi biết nhưng tôi trả lời điều này vì tôi nghĩ giải pháp này đơn giản và thanh lịch:

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

Tất cả điều này là để cập nhật từ danh sách này sang danh sách khác và bạn có thể tạo tất cả chỉ từ một danh sách và trong phương pháp cập nhật, bạn kiểm tra cả danh sách và có thể xóa hoặc thêm các yếu tố danh sách betwen. Điều này có nghĩa là cả hai danh sách luôn có cùng kích thước


0

Sử dụng Iterator thay vì Array List

Có một bộ được chuyển đổi thành iterator với kiểu khớp

Và di chuyển đến phần tử tiếp theo và loại bỏ

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

Di chuyển đến tiếp theo là quan trọng ở đây vì nó cần lấy chỉ mục để loại bỏ phần tử.


0

Còn về

import java.util.Collections;

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

-3

Chỉ cần thêm dấu ngắt sau câu lệnh ArrayList.remove (A) của bạn


Bạn có thể vui lòng thêm một số lời giải thích?
xskxzr
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.