[java] 반복하는 동안 컬렉션에서 요소 제거

AFAIK에는 두 가지 접근 방식이 있습니다.

  1. 컬렉션의 사본을 반복
  2. 실제 콜렉션의 반복자를 사용하십시오.

예를 들어

List<Foo> fooListCopy = new ArrayList<Foo>(fooList);
for(Foo foo : fooListCopy){
    // modify actual fooList
}

Iterator<Foo> itr = fooList.iterator();
while(itr.hasNext()){
    // modify actual fooList using itr.remove()
}

한 가지 접근 방식을 다른 접근 방식보다 선호해야하는 이유가 있습니까 (예 : 가독성의 간단한 이유로 첫 번째 접근 방식을 선호)?



답변

을 피하기위한 몇 가지 대안으로 몇 가지 예를 드리겠습니다 ConcurrentModificationException.

우리가 다음과 같은 책을 가지고 있다고 가정 해보십시오.

List<Book> books = new ArrayList<Book>();
books.add(new Book(new ISBN("0-201-63361-2")));
books.add(new Book(new ISBN("0-201-63361-3")));
books.add(new Book(new ISBN("0-201-63361-4")));

수집 및 제거

첫 번째 기술은 삭제하려는 모든 객체를 수집하는 것입니다 (예 : 향상된 for 루프 사용). 반복을 마치면 찾은 모든 객체를 제거합니다.

ISBN isbn = new ISBN("0-201-63361-2");
List<Book> found = new ArrayList<Book>();
for(Book book : books){
    if(book.getIsbn().equals(isbn)){
        found.add(book);
    }
}
books.removeAll(found);

이것은 당신이하고 싶은 조작이 “삭제”라고 가정합니다.

이 방법을 “추가”하려는 경우에도 효과가 있지만 다른 컬렉션을 반복하여 두 번째 컬렉션에 추가 할 요소를 결정한 다음 addAll마지막에 메소드 를 발행한다고 가정합니다 .

ListIterator 사용

리스트로 작업하는 경우 ListIterator, 반복 기법 중에 항목을 제거하고 추가 할 수 있는 기능을 사용하는 다른 기술이 사용 됩니다.

ListIterator<Book> iter = books.listIterator();
while(iter.hasNext()){
    if(iter.next().getIsbn().equals(isbn)){
        iter.remove();
    }
}

다시 한 번, 위 예제에서 “remove”메소드를 사용했는데, 이는 질문에 암시 된 것처럼 보이지만이 add메소드를 사용하여 반복 중에 새 요소를 추가 할 수도 있습니다 .

JDK 사용> = 8

Java 8 이상 버전을 사용하는 사용자에게는이를 활용할 수있는 몇 가지 다른 기술이 있습니다.

기본 클래스 removeIf에서 새 메소드를 사용할 수 있습니다 Collection.

ISBN other = new ISBN("0-201-63361-2");
books.removeIf(b -> b.getIsbn().equals(other));

또는 새로운 스트림 API를 사용하십시오.

ISBN other = new ISBN("0-201-63361-2");
List<Book> filtered = books.stream()
                           .filter(b -> b.getIsbn().equals(other))
                           .collect(Collectors.toList());

이 마지막 경우, 콜렉션에서 요소를 필터링하려면 원래 참조를 필터링 된 콜렉션 ( books = filtered)에 재 지정 하거나 필터링 된 콜렉션을 removeAll원래 콜렉션 (즉 books.removeAll(filtered)) 에서 찾은 요소에 사용하십시오 .

하위 목록 또는 하위 집합 사용

다른 대안도 있습니다. 목록이 정렬되어 있고 연속 요소를 제거하려는 경우 하위 목록을 작성하고 지울 수 있습니다.

books.subList(0,5).clear();

서브리스트는 원래리스트에 의해 지원되므로이 서브 요소 콜렉션을 제거하는 효율적인 방법입니다.

NavigableSet.subSet방법을 사용하여 정렬 된 세트 또는 여기에 제공된 슬라이싱 방법을 사용하여 유사한 것을 얻을 수 있습니다 .

고려 사항 :

사용하려는 방법은 수행하려는 작업에 따라 다를 수 있습니다

  • 수집 및 removeAl기술은 모든 컬렉션 (컬렉션, 목록, 세트 등)과 함께 작동합니다.
  • ListIterator기술은 분명히 그들의 주어진 것을 제공,리스트와 함께 작동 ListIterator구현 이벤트 추가 및 제거 작업에 지원합니다.
  • Iterator접근 방식은 모든 유형의 컬렉션에서 작동하지만 제거 작업 만 지원합니다.
  • ListIterator/ Iterator접근 방식을 사용하면 반복 할 때 제거하기 때문에 아무것도 복사 할 필요가 없습니다. 따라서 이것은 매우 효율적입니다.
  • JDK 8 스트림 예제는 실제로 아무것도 제거하지 않았지만 원하는 요소를 찾은 다음 원래 컬렉션 참조를 새 것으로 교체하고 이전 컬렉션을 가비지 수집합니다. 따라서 컬렉션을 한 번만 반복하면 효율적입니다.
  • 수집 및 removeAll접근 방식의 단점은 두 번 반복해야한다는 것입니다. 먼저 제거 루프에서 제거 기준과 일치하는 객체를 찾기 위해 루프에서 반복하고, 일단 찾은 후에는 원래 컬렉션에서 객체를 제거하도록 요청합니다. 이는 다음 항목을 찾기 위해이 항목을 찾기위한 두 번째 반복 작업을 의미합니다. 제거하십시오.
  • Iterator인터페이스 의 remove 메소드가 Javadocs에서 “선택적”으로 표시 된다는 것을 언급 할 가치가 있다고 생각합니다 . 이는 remove 메소드를 호출하면 Iterator구현 이 발생할 수 있음을 의미합니다 UnsupportedOperationException. 따라서 요소 제거를위한 반복자 지원을 보장 할 수없는 경우이 방법이 다른 방법보다 안전하지 않다고 말하고 싶습니다.

답변

Java 8에는 또 다른 접근법이 있습니다. 컬렉션 #removeIf

예 :

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

list.removeIf(i -> i > 2);


답변

한 가지 접근법을 다른 접근법보다 선호하는 이유가 있습니까?

첫 번째 방법은 효과가 있지만 목록을 복사하는 명백한 오버 헤드가 있습니다.

많은 컨테이너가 반복 중에 수정을 허용하지 않기 때문에 두 번째 방법은 작동하지 않습니다. 이 포함됩니다ArrayList .

유일한 수정은 현재 요소를 제거하는 경우, 당신은 사용하여 두 번째 방법 작업을 할 수 있습니다 itr.remove()(즉, 사용하는 반복자를remove()방법이 아닌 용기 의). 이것은 지원하는 반복자에 대해 내가 선호하는 방법입니다 remove().


답변

두 번째 접근 방식 만 작동합니다. 반복하는 동안 iterator.remove()만 콜렉션을 수정할 수 있습니다 . 다른 모든 시도는 원인이 ConcurrentModificationException됩니다.


답변

이전 타이머 즐겨 찾기 (아직 작동) :

List<String> list;

for(int i = list.size() - 1; i >= 0; --i)
{
        if(list.get(i).contains("bad"))
        {
                list.remove(i);
        }
}


답변

당신이 사용하는 경우에도 때문에, 두 번째 할 수 없습니다 remove()에 대한 방법 Iterator를 , 당신이 던져 예외를 얻을 수 있습니다 .

개인적으로, 나는 Collection새로운 인스턴스 생성에 대한 추가 소식에도 불구하고 모든 인스턴스에 대해 첫 번째를 선호하지만 Collection다른 개발자가 편집하는 동안 오류가 발생하기 쉽습니다. 일부 Collection 구현에서는 Iterator remove()가 지원되지만 그렇지 않은 경우에는 Iterator 가 지원됩니다. Iterator 관련 문서에서 더 많은 내용을 읽을 수 있습니다 .

세 번째 대안은 새를 만드는 것입니다 Collection원래 이상 반복, 그리고 첫 번째의 모든 구성원을 추가 Collection두 번째에 Collection있습니다 없습니다 까지 삭제. Collection삭제 크기 및 삭제 수에 따라 첫 번째 방법과 비교할 때 메모리를 크게 절약 할 수 있습니다.


답변

메모리 사본을 할 필요가 없으므로 Iterator가 더 빨리 작동하므로 두 번째를 선택합니다. 따라서 메모리와 시간을 절약 할 수 있습니다.