web-dev-qa-db-ja.com

Java 8ストリームforEachでif-elseロジックを使用する方法

私がやりたいことは、2つのストリーム呼び出しで以下に示されています。ある条件に基づいて、コレクションを2つの新しいコレクションに分割します。理想的には、1でそれを行いたいと思います。ストリームの.map関数に使用される条件を見てきましたが、forEachには何も見つかりませんでした。私が望むものを達成するための最良の方法は何ですか?

    animalMap.entrySet().stream()
            .filter(pair-> pair.getValue() != null)
            .forEach(pair-> myMap.put(pair.getKey(), pair.getValue()));

    animalMap.entrySet().stream()
            .filter(pair-> pair.getValue() == null)
            .forEach(pair-> myList.add(pair.getKey()));
36
user3768533

条件をラムダ自体に入れるだけです。

animalMap.entrySet().stream()
        .forEach(
                pair -> {
                    if (pair.getValue() != null) {
                        myMap.put(pair.getKey(), pair.getValue());
                    } else {
                        myList.add(pair.getKey());
                    }
                }
        );

もちろん、これは両方のコレクション(myMapおよびmyList)が上記のコードの前に宣言および初期化されることを前提としています。


更新:Map.forEachを使用すると、 Jorn Vernee が示唆するように、コードが短くなり、より効率的で読みやすくなります。

    animalMap.forEach(
            (key, value) -> {
                if (value != null) {
                    myMap.put(key, value);
                } else {
                    myList.add(key);
                }
            }
    );
66
Alex Shesterov

ほとんどの場合、ストリームでforEachを使用していることに気付いたら、ジョブに適切なツールを使用しているか、それとも正しい方法で使用しているかを再考する必要があります。

一般的に、達成したいことをしている適切な端末操作または適切なコレクターを探す必要があります。現在、MapsおよびListsを生成するコレクターがありますが、述部に基づいて2つの異なるコレクターを結合するためのすぐに使用できるコレクターはありません。

現在、 この回答 には2つのコレクターを結合するためのコレクターが含まれています。このコレクターを使用すると、次のようにタスクを達成できます。

Pair<Map<KeyType, Animal>, List<KeyType>> pair = animalMap.entrySet().stream()
    .collect(conditional(entry -> entry.getValue() != null,
            Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue),
            Collectors.mapping(Map.Entry::getKey, Collectors.toList()) ));
Map<KeyType,Animal> myMap = pair.a;
List<KeyType> myList = pair.b;

しかし、おそらく、この特定のタスクをより簡単な方法で解決できます。いずれかの結果が入力タイプと一致します。 nullにマップするエントリを削除した同じマップです。元のマップが変更可能で、後で必要ない場合は、リストを収集し、これらのキーは相互に排他的であるため、元のマップから削除できます。

List<KeyType> myList=animalMap.entrySet().stream()
    .filter(pair -> pair.getValue() == null)
    .map(Map.Entry::getKey)
    .collect(Collectors.toList());

animalMap.keySet().removeAll(myList);

他のキーのリストがなくても、nullへのマッピングを削除できることに注意してください。

animalMap.values().removeIf(Objects::isNull);

または

animalMap.values().removeAll(Collections.singleton(null));

元の地図を変更できない(または変更したくない)場合でも、カスタムコレクターなしの解決策があります。 Alexis Cの答え で示唆されているように、partitioningByは正しい方向に進んでいますが、単純化することもできます。

Map<Boolean,Map<KeyType,Animal>> tmp = animalMap.entrySet().stream()
    .collect(Collectors.partitioningBy(pair -> pair.getValue() != null,
                 Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)));
Map<KeyType,Animal> myMap = tmp.get(true);
List<KeyType> myList = new ArrayList<>(tmp.get(false).keySet());

要するに、通常のコレクション操作を忘れないでください。新しいStream APIですべてを行う必要はありません。

11
Holger

addまたはputforEach内で呼び出してstream().forEach(..)を使用することによる問題(したがって、外部myMapまたはmyListインスタンスを変更します)誰かがストリームを並行して回し、変更しているコレクションがスレッドセーフでない場合、同時実行の問題に簡単に遭遇する可能性があります。

可能なアプローチの1つは、最初に元のマップのエントリをパーティション分割することです。それができたら、対応するエントリのリストを取得し、適切なマップとリストに収集します。

Map<Boolean, List<Map.Entry<K, V>>> partitions =
    animalMap.entrySet()
             .stream()
             .collect(partitioningBy(e -> e.getValue() == null));

Map<K, V> myMap = 
    partitions.get(false)
              .stream()
              .collect(toMap(Map.Entry::getKey, Map.Entry::getValue));

List<K> myList =
    partitions.get(true)
              .stream()
              .map(Map.Entry::getKey) 
              .collect(toList());

...または、1回のパスでそれを行いたい場合は、カスタムコレクターを実装します(Tuple2<E1, E2>クラスが存在する場合、独自のコレクターを作成できます)、例えば:

public static <K,V> Collector<Map.Entry<K, V>, ?, Tuple2<Map<K, V>, List<K>>> customCollector() {
    return Collector.of(
            () -> new Tuple2<>(new HashMap<>(), new ArrayList<>()),
            (pair, entry) -> {
                if(entry.getValue() == null) {
                    pair._2.add(entry.getKey());
                } else {
                    pair._1.put(entry.getKey(), entry.getValue());
                }
            },
            (p1, p2) -> {
                p1._1.putAll(p2._1);
                p1._2.addAll(p2._2);
                return p1;
            });
}

その使用法で:

Tuple2<Map<K, V>, List<K>> pair = 
    animalMap.entrySet().parallelStream().collect(customCollector());

必要に応じて、たとえばパラメーターとして述部を提供することにより、さらに調整できます。

7
Alexis C.

Java 9で可能だと思います:

animalMap.entrySet().stream()
                .forEach(
                        pair -> Optional.ofNullable(pair.getValue())
                                .ifPresentOrElse(v -> myMap.put(pair.getKey(), v), v -> myList.add(pair.getKey())))
                );

ただし、動作するにはifPresentOrElseが必要です。 (forループのほうが見栄えが良いと思います。)

6
user3337629