web-dev-qa-db-ja.com

Javaセットを反復および変更するにはどうすればよいですか?

整数のセットがあり、セット内のすべての整数をインクリメントしたいとします。どうすればいいですか?

繰り返しながら要素をセットに追加したり削除したりできますか?

元のセットを繰り返しながら、要素を「コピーして変更」する新しいセットを作成する必要がありますか?

編集:セットの要素が不変である場合はどうなりますか?

76
Buttons840

Iteratorオブジェクトを使用して、反復中にセットから安全に削除できます。反復中にAPIを介してセットを変更しようとすると、反復子が壊れます。 Setクラスは、getIterator()を通じてイテレーターを提供します。

ただし、整数オブジェクトは不変です。私の戦略は、セット全体を反復処理し、整数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);
89

イテレータオブジェクトを使用してセット内の要素を調べると、必要な処理を実行できます。外出先でそれらを削除できます。ただし、forループ(「標準」、for eachのいずれか)でそれらを削除すると、問題が発生します。

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で明確に規定されているため、「悪い」方法は悪いと言えます。 docs:

https://docs.Oracle.com/javase/8/docs/api/Java/util/ConcurrentModificationException.html

とりわけ、(強調鉱山)を規定する:

"たとえば、一般に、あるスレッドがコレクションを変更している間に別のスレッドがコレクションを変更することは一般的に許可されていません。一般に、繰り返しの結果一部のイテレータ実装(JREが提供するすべての汎用コレクション実装の実装を含む)は、この動作が検出された場合にこの例外をスローすることを選択できます。(...)

この例外は、オブジェクトが異なるスレッドによって同時に変更されたことを常に示すわけではないことに注意してください。単一のスレッドがオブジェクトのコントラクトに違反するメソッド呼び出しのシーケンスを発行する場合、オブジェクトたとえば、フェイルファストイテレータでコレクションを反復処理している間にスレッドがコレクションを直接変更すると、イテレータはこの例外をスローします。」

より詳細に説明するには、forEachループで使用できるオブジェクトは、「Java.lang.Iterable」インターフェース(javadoc here )を実装する必要があります。これにより Iterator が生成され(このインターフェイスにある「Iterator」メソッドを使用)、オンデマンドでインスタンス化され、作成元のIterableオブジェクトへの参照が内部的に含まれます。ただし、IterableオブジェクトがforEachループで使用されている場合、このイテレーターのインスタンスはユーザーに対して非表示になります(自分でアクセスすることはできません)。

これは、イテレータが非常にステートフルであるという事実と相まって、つまり、マジックを実行し、「next」および「hasNext」メソッドに対して一貫した応答を得るために、バッキングオブジェクトがイテレータ自体以外によって変更されないことが必要です。反復中に、バッキングオブジェクトで何かが変更されたことを検出するとすぐに例外をスローするようにします。

Javaはこの「フェイルファースト」イテレーションを呼び出します。つまり、通常はIterableインスタンスを変更するアクションがいくつかあります(イテレータがイテレータを反復処理している間に)。 「fail-fast」概念の「fail」部分は、そのような「fail」アクションがいつ発生するかを検出するIteratorの機能を指します。 「fail-fast」の「fast」部分(および、私の意見では「best-effort-fast」と呼ばれるべきです)は、ConcurrentModificationExceptionを介してすぐに反復を終了しますは「失敗」アクションが発生したことを検出できるためです。

39
Shivan Dragon

イテレータのセマンティックはあまり好きではありません。これをオプションとして検討してください。また、公開する内部状態が少ないため、安全です。

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;

}
4
snovelli

第一に、一度にいくつかのことをしようとすることは一般的に悪い習慣であると信じています。

それは良い理論的な質問として機能しますが、私が集めたものからJava.util.SetインターフェースのCopyOnWriteArraySet実装はあなたのかなり特別な要件を満たします。

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

0
MarianP

プリミティブ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メソッドを実装する必要がありますが、これはこの回答の範囲外です。

0