私がやりたいことは、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()));
条件をラムダ自体に入れるだけです。
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);
}
}
);
ほとんどの場合、ストリームでforEach
を使用していることに気付いたら、ジョブに適切なツールを使用しているか、それとも正しい方法で使用しているかを再考する必要があります。
一般的に、達成したいことをしている適切な端末操作または適切なコレクターを探す必要があります。現在、Map
sおよびList
sを生成するコレクターがありますが、述部に基づいて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ですべてを行う必要はありません。
add
またはput
をforEach
内で呼び出して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());
必要に応じて、たとえばパラメーターとして述部を提供することにより、さらに調整できます。
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ループのほうが見栄えが良いと思います。)