Javaセットを繰り返して変更するにはどうすればよいですか?


80

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

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

元のセットを反復処理しているときに、要素を「コピーして変更」する新しいセットを作成する必要がありますか?

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

回答:


92

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

1
元のセットをガベージコレクターに残して、新しいセットを使い続ける方が簡単ではないでしょうか。一時セットが収集されるか、元のセットのどちらかです。私も一時セットを保持し、元のセットを変更する必要はありませんか?しかし、私の場合、元のセットが最終的なものであったため、これは不可能だったので、受け入れました。
buttons840 2011年

@JonathanWeatherhead 2番目の単語がでcannotはなくあるという意味canですか?のように、You cannot safely removeではなくYou can safely remove?その最初の段落の矛盾のようです。
バジルバーク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 2018

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

とりわけ、次のように規定します(私の強調):

たとえば、あるスレッドがコレクションを反復しているときに、あるスレッドがコレクションを変更することは一般に許可されていません。一般に、反復の結果はこのような状況では定義されていません。一部のIterator実装(すべての汎用コレクションの実装を含む) JREによって提供される実装)は、この動作が検出された場合にこの例外をスローすることを選択できます」(...)

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

詳細については、forEachループで使用できるオブジェクトに「java.lang.Iterable」インターフェイス(ここではjavadoc )を実装する必要があります。これは、生成イテレータをオンデマンドでインスタンス化され、そして内部それが作成された反復処理可能オブジェクトへの参照を含むことになる(このインタフェースに見出さ「イテレータ」法を介して)。ただし、IterableオブジェクトがforEachループで使用されている場合、このイテレーターのインスタンスはユーザーに非表示になります(自分でアクセスすることはできません)。

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

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


1
悪い方法が失敗する理由を明確にできますか?
mrgloom 2017年

@mrgloom基本的に、Iterableオブジェクトで動作しているスレッドが1つしかない場合でもConcurrentModificationExceptionがスローされる理由についての詳細を追加しました
Shivan Dragon

場合によっては、java.lang.UnsupportedOperationException
Aguid 2017年

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


0

まず、一度にいくつかのことをやろうとするのは一般的に悪い習慣だと思います。何を達成しようとしているのかを考えてみることをお勧めします。

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

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.