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

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

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

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

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



답변

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


답변

반복기 객체를 사용하여 세트의 요소를 탐색하는 경우 원하는 작업을 수행 할 수 있습니다. 이동 중에도 제거 할 수 있습니다. 그러나 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 통해 반복을 종료합니다. 우연히 있다.


답변

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

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;

}


답변

프리미티브 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 메서드를 구현해야하지만이 답변의 범위를 벗어납니다.


답변

첫째, 한 번에 여러 가지를 시도하는 것은 일반적으로 나쁜 습관이라고 생각하며 달성하려는 목표에 대해 생각할 것을 제안합니다.

그것은 좋은 이론적 질문으로 작용하며 내가 수집 한 인터페이스 CopyOnWriteArraySet구현은 java.util.Set당신의 특별한 요구 사항을 충족시킵니다.

http://download.oracle.com/javase/1,5.0/docs/api/java/util/concurrent/CopyOnWriteArraySet.html


답변