2009-07-10 65 views
98

Vì vậy, nếu tôi cố gắng xóa các phần tử khỏi Java HashSet trong khi lặp lại, tôi nhận được ConcurrentModificationException. Cách tốt nhất để xóa tập hợp con của các phần tử khỏi HashSet như trong ví dụ sau là gì?Xóa các phần tử khỏi một HashSet trong khi Iterating

Set<Integer> set = new HashSet<Integer>(); 

for(int i = 0; i < 10; i++) 
    set.add(i); 

// Throws ConcurrentModificationException 
for(Integer element : set) 
    if(element % 2 == 0) 
     set.remove(element); 

Dưới đây là một giải pháp, nhưng tôi không nghĩ rằng nó rất tao nhã:

Set<Integer> set = new HashSet<Integer>(); 
Collection<Integer> removeCandidates = new LinkedList<Integer>(); 

for(int i = 0; i < 10; i++) 
    set.add(i); 

for(Integer element : set) 
    if(element % 2 == 0) 
     removeCandidates.add(element); 

set.removeAll(removeCandidates); 

Cảm ơn!

Trả lời

154

Bạn có thể tự lặp qua các yếu tố của thiết lập:

Iterator<Integer> iterator = set.iterator(); 
while (iterator.hasNext()) { 
    Integer element = iterator.next(); 
    if (element % 2 == 0) { 
     iterator.remove(); 
    } 
} 

Bạn thường sẽ thấy mô hình này sử dụng một vòng lặp for chứ không phải là một vòng lặp while:

for (Iterator<Integer> i = set.iterator(); i.hasNext();) { 
    Integer element = i.next(); 
    if (element % 2 == 0) { 
     i.remove(); 
    } 
} 

Khi mọi người đã chỉ ra , sử dụng vòng lặp for được ưu tiên vì nó giữ biến vòng lặp (i trong trường hợp này) được giới hạn trong phạm vi nhỏ hơn.

+5

Tôi thích 'for' đến' while', nhưng mỗi cái lại là của riêng mình. –

+1

Tôi cũng sử dụng 'for'. Tôi đã sử dụng 'while' để làm cho ví dụ rõ ràng hơn. –

+14

Tôi perfer 'for' chủ yếu là do biến iterator sau đó được giới hạn trong phạm vi của vòng lặp. –

9

bạn cũng có thể cấu trúc lại giải pháp của bạn loại bỏ các vòng đầu tiên:

Set<Integer> set = new HashSet<Integer>(); 
Collection<Integer> removeCandidates = new LinkedList<Integer>(set); 

for(Integer element : set) 
    if(element % 2 == 0) 
     removeCandidates.add(element); 

set.removeAll(removeCandidates); 
+0

mẹo tuyệt vời, cảm ơn – Buffalo

+0

Tôi sẽ không khuyên bạn nên điều này vì nó giới thiệu một khớp nối thời gian ẩn. –

+1

@RomainF. - Bạn có ý nghĩa gì bởi sự liên kết thời gian ẩn? Bạn có nghĩa là chủ đề an toàn? Thứ hai, tôi cũng không khuyên bạn nên dùng giải pháp này nhưng giải pháp của nó lại có tính chuyên nghiệp. Siêu dễ đọc và do đó có thể duy trì. – saurabheights

4

Liệu nó cần phải được trong khi iterating? Nếu tất cả những gì bạn đang làm là lọc hoặc chọn, tôi khuyên bạn nên sử dụng Apache Commons CollectionUtils. Có một số công cụ mạnh mẽ ở đó và nó làm cho mã của bạn "mát mẻ hơn".

Dưới đây là một thực hiện mà nên cung cấp những gì bạn cần:

Set<Integer> myIntegerSet = new HashSet<Integer>(); 
// Integers loaded here 
CollectionUtils.filter(myIntegerSet, new Predicate() { 
           public boolean evaluate(Object input) { 
            return (((Integer) input) % 2 == 0); 
           }}); 

Nếu bạn thấy mình bằng cách sử dụng cùng một loại ngữ thường xuyên bạn có thể kéo mà ra vào một biến tĩnh để tái sử dụng ... tên nó như một cái gì đó EVEN_NUMBER_PREDICATE. Một số có thể thấy mã đó và tuyên bố nó "khó đọc" nhưng nó trông sạch hơn khi bạn rút ra các Predicate thành một tĩnh. Sau đó, thật dễ dàng để thấy rằng chúng tôi đang thực hiện một CollectionUtils.filter(...) và điều đó có vẻ dễ đọc hơn (đối với tôi) hơn là một loạt các vòng lặp trong quá trình tạo.

+0

Câu trả lời này thực sự bắt đầu hiển thị tuổi của nó ... Có một cách Java-8 để làm điều này bây giờ được cho là sạch hơn. – dustmachine

16

Lý do bạn nhận được một ConcurrentModificationException là vì một mục bị xóa qua Set.remove() như trái ngược với Iterator.remove(). Nếu một mục được xóa qua Set.remove() trong khi lặp lại đang được thực hiện, bạn sẽ nhận được một ConcurrentModificationException. Mặt khác, việc xóa các mục nhập thông qua Iterator.remove() trong khi lặp lại được hỗ trợ trong trường hợp này.

Vòng lặp mới rất đẹp, nhưng tiếc là nó không hoạt động trong trường hợp này, vì bạn không thể sử dụng tham chiếu Iterator.

Nếu bạn cần xóa mục nhập trong khi lặp lại, bạn cần sử dụng biểu mẫu dài sử dụng Iterator trực tiếp.

for (Iterator<Integer> it = set.iterator(); it.hasNext();) { 
    Integer element = it.next(); 
    if (element % 2 == 0) { 
     it.remove(); 
    } 
} 
+0

@ Không nên mã của bạn thực sự gọi nó.next()? – saurabheights

+1

Cảm ơn vì điều đó. Đã sửa. – sjlee

+0

Tại thời điểm nào 'yếu tố' được khởi tạo? –

2

Một giải pháp khả thi khác:

for(Object it : set.toArray()) { /* Create a copy */ 
    Integer element = (Integer)it; 
    if(element % 2 == 0) 
     set.remove(element); 
} 

Hoặc:

Integer[] copy = new Integer[set.size()]; 
set.toArray(copy); 

for(Integer element : copy) { 
    if(element % 2 == 0) 
     set.remove(element); 
} 
+0

Điều đó (hoặc tạo một 'ArrayList' ra khỏi tập hợp) là giải pháp tốt nhất nếu bạn không chỉ loại bỏ các phần tử hiện có mà còn thêm các phần tử mới vào tập hợp trong vòng lặp. –

6

Java 8 Bộ sưu tập có một phương pháp tốt đẹp gọi là removeIf mà làm cho mọi thứ dễ dàng hơn và an toàn hơn. Từ các tài liệu API:

default boolean removeIf(Predicate<? super E> filter) 
Removes all of the elements of this collection that satisfy the given predicate. 
Errors or runtime exceptions thrown during iteration or by the predicate 
are relayed to the caller. 

note Thú vị:

The default implementation traverses all elements of the collection using its iterator(). 
Each matching element is removed using Iterator.remove(). 

từ: https://docs.oracle.com/javase/8/docs/api/java/util/Collection.html#removeIf-java.util.function.Predicate-

+0

Ví dụ: 'integerSet.removeIf (integer-> integer.equals (5));' – Jelle

4

Giống như gỗ nói - "Java 8 Bộ sưu tập có một phương pháp tốt đẹp gọi là removeIf mà làm cho mọi thứ dễ dàng hơn và an toàn hơn "

Đây là mã giải quyết vấn đề của bạn:

set.removeIf((Integer element) -> { 
    return (element % 2 == 0); 
}); 

Bây giờ, tập hợp của bạn chỉ chứa các giá trị lẻ.

Các vấn đề liên quan