Java 세트를 어떻게 반복하고 수정합니까?


80

정수 집합이 있고 집합의 모든 정수를 증가시키고 싶다고 가정 해 보겠습니다. 어떻게해야합니까?

반복하는 동안 세트에서 요소를 추가하고 제거 할 수 있습니까?

원래 세트를 반복하는 동안 요소를 "복사 및 수정"할 새 세트를 만들어야합니까?

편집 : 세트의 요소가 불변이면 어떻게됩니까?

답변:


92

Iterator 개체를 사용하여 반복하는 동안 집합에서 안전하게 제거 할 수 있습니다. 반복하는 동안 API를 통해 집합을 수정하려고하면 반복기가 중단됩니다. Set 클래스는 getIterator ()를 통해 반복자를 제공합니다.

그러나 Integer 객체는 변경할 수 없습니다. 내 전략은 세트를 반복하고 각 Integer i에 대해 새 임시 세트에 i + 1을 추가하는 것입니다. 반복이 끝나면 원래 세트에서 모든 요소를 ​​제거하고 새 임시 세트의 모든 요소를 ​​추가하십시오.

Set<Integer> s; //contains your Integers
...
Set<Integer> temp = new Set<Integer>();
for(Integer i : s)
    temp.add(i+1);
s.clear();
s.addAll(temp);

1
원래 세트를 가비지 수집기에 남겨두고 새 세트를 계속 사용하는 것이 더 쉬울까요? 임시 세트가 수집되거나 원래 세트 중 하나, 내뿐만 아니라 임시 세트를 유지하고 원본을 변경하지 않습니까? 그러나 원래 세트가 최종 세트이기 때문에 제 경우에는 불가능했기 때문에 수락했습니다.
Buttons840 2011

@JonathanWeatherhead 그게 cannot아니라 두 번째 단어를 의미 했습니까 can? 의로서 You cannot safely remove가 아니라 You can safely remove? 첫 번째 단락에서 모순처럼 보입니다.
Basil Bourque 2014 년

@BasilBourque 아니에요. Iterator :: remove () 메서드는 문서 상태로 안전합니다. docs.oracle.com/javase/7/docs/api/java/util/Iterator.html
Jonathan Weatherhead

error: incompatible types: Object cannot be converted to Integer
alhelal

40

반복기 객체를 사용하여 세트의 요소를 탐색하는 경우 원하는 작업을 수행 할 수 있습니다. 이동 중에도 제거 할 수 있습니다. 그러나 for 루프 (각 종류의 "표준")에서 제거하면 문제가 발생합니다.

Set<Integer> set = new TreeSet<Integer>();
    set.add(1);
    set.add(2);
    set.add(3);

    //good way:
    Iterator<Integer> iterator = set.iterator();
    while(iterator.hasNext()) {
        Integer setElement = iterator.next();
        if(setElement==2) {
            iterator.remove();
        }
    }

    //bad way:
    for(Integer setElement:set) {
        if(setElement==2) {
            //might work or might throw exception, Java calls it indefined behaviour:
            set.remove(setElement);
        } 
    }

@mrgloom의 의견에 따라 위에서 설명한 "나쁜"방식이 왜 나쁜지에 대한 자세한 내용은 다음과 같습니다.

Java가이를 구현하는 방법에 대해 너무 자세하게 설명하지 않고 높은 수준에서 "나쁜"방법은 Java 문서에 명시되어 있기 때문에 나쁘다고 말할 수 있습니다.

https://docs.oracle.com/javase/8/docs/api/java/util/ConcurrentModificationException.html

무엇보다도 다음과 같이 규정하십시오 (내 강조) :

" 예를 들어 한 스레드가 Collection을 수정하는 동안 다른 스레드가 컬렉션을 반복하는 것은 일반적으로 허용되지 않습니다. 일반적으로 이러한 상황에서는 반복 결과가 정의되지 않습니다. 일부 Iterator 구현 (모든 범용 컬렉션의 구현 포함) JRE에서 제공하는 구현)은이 동작이 감지되면이 예외를 throw하도록 선택할 수 있습니다. "(...)

" 이 예외가 항상 다른 스레드에 의해 객체가 동시에 수정되었음을 나타내는 것은 아닙니다. 단일 스레드가 객체의 계약을 위반하는 일련의 메서드 호출을 실행하면 객체에서이 예외를 throw 할 수 있습니다. 예를 들어, 스레드는 실패-빠른 반복기를 사용하여 콜렉션을 반복하는 동안 콜렉션을 직접 수정합니다. 반복자는이 예외를 발생시킵니다. "

자세히 알아 보려면 forEach 루프에서 사용할 수있는 객체는 "java.lang.Iterable"인터페이스를 구현해야합니다 ( 여기에서는 javadoc ). 이는 요청시 인스턴스화되고 생성 된 Iterable 객체에 대한 참조를 내부적으로 포함 하는 이터레이터 (이 인터페이스에있는 "Iterator"메소드를 통해)를 생성합니다. 그러나 Iterable 객체가 forEach 루프에서 사용되는 경우이 반복기의 인스턴스는 사용자에게 숨겨집니다 (어떤 방식 으로든 직접 액세스 할 수 없음).

이것은 Iterator가 꽤 stateful하다는 사실과 결합됩니다. 즉, 마법을 수행하고 "next"및 "hasNext"메서드에 대한 일관된 응답을 갖기 위해서는 백업 객체가 반복자 자체가 아닌 다른 것에 의해 변경되지 않아야합니다. 반복하는 동안 반복하는 동안 백업 객체에서 변경된 사항을 감지하는 즉시 예외를 throw하도록 만듭니다.

자바는 이것을 "fail-fast"반복이라고 부릅니다. 즉, 일반적으로 Iterable 인스턴스를 수정하는 액션이 ​​있습니다 (반복자가 반복하는 동안). "fail-fast"개념의 "실패"부분은 이러한 "실패"동작이 발생하는시기를 감지하는 반복자의 기능을 나타냅니다. "fail-fast"의 "fast"부분 (내 생각에는 "best-effort-fast"라고 부름) 은 "fail"작업이 다음 과 같은 것을 감지수있는 즉시 ConcurrentModificationException 통해 반복을 종료합니다. 우연히 있다.


1
왜 나쁜 길이 실패하는지 명확히 할 수 있습니까?
mrgloom

@mrgloom 기본적으로 Iterable 개체에서 작동하는 스레드가 하나 뿐인 경우에도 ConcurrentModificationException이 throw 될 수있는 이유에 대한 자세한 내용을 추가했습니다
Shivan Dragon

어떤 경우에는 java.lang.UnsupportedOperationException
Aguid

4

반복자의 의미론이별로 마음에 들지 않습니다. 이것을 옵션으로 고려하십시오. 또한 내부 상태를 덜 게시할수록 더 안전합니다.

private Map<String, String> JSONtoMAP(String jsonString) {

    JSONObject json = new JSONObject(jsonString);
    Map<String, String> outMap = new HashMap<String, String>();

    for (String curKey : (Set<String>) json.keySet()) {
        outMap.put(curKey, json.getString(curKey));
    }

    return outMap;

}

0

프리미티브 int의 가변 래퍼를 만들고 그 세트를 만들 수 있습니다.

class MutableInteger
{
    private int value;
    public int getValue()
    {
        return value;
    }
    public void setValue(int value)
    {
        this.value = value;
    }
}

class Test
{
    public static void main(String[] args)
    {
        Set<MutableInteger> mySet = new HashSet<MutableInteger>();
        // populate the set
        // ....

        for (MutableInteger integer: mySet)
        {
            integer.setValue(integer.getValue() + 1);
        }
    }
}

물론 HashSet을 사용하는 경우 MutableInteger에서 hash, equals 메서드를 구현해야하지만이 답변의 범위를 벗어납니다.


당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.