web-dev-qa-db-ja.com

Java 8 Streamsはコレクション内のアイテムを操作してから削除できますか?

ほぼすべての人と同じように、私はまだ新しいJava 8 Streams APIの複雑さ(およびそれらを愛していること)を学んでいます。ストリームの使用に関する質問があります。簡単な例を示します。

Java Streamsでは、Collectionを取得し、stream()メソッドを使用して、すべての要素のストリームを受信できます。その中には、filter()map()forEach()などの便利なメソッドがいくつかあり、コンテンツに対してラムダ操作を使用できます。

次のようなコードがあります(簡略化されています)。

set.stream().filter(item -> item.qualify())
    .map(item -> (Qualifier)item).forEach(item -> item.operate());
set.removeIf(item -> item.qualify());

アイデアは、特定の修飾子に一致するセット内のすべてのアイテムのマッピングを取得し、それらを操作することです。操作後、それらはそれ以上の目的を果たさず、元のセットから削除する必要があります。コードはうまく機能しますが、Streamに1行でこれを実行できる操作があるという感覚を揺るがすことはできません。

Javadocsにある場合は、見落としている可能性があります。

APIに精通している人はそのようなものを見ていますか?

65

次のようにできます:

set.removeIf(item -> {
    if (!item.qualify())
        return false;
    item.operate();
    return true;
});

item.operate()が常にtrueを返す場合、非常に簡潔に行うことができます。

set.removeIf(item -> item.qualify() && item.operate());

しかし、私はこれらのアプローチが好きではありません。何が起こっているのかがすぐにはわからないからです。個人的には、forループとIteratorを引き続き使用します。

for (Iterator<Item> i = set.iterator(); i.hasNext();) {
    Item item = i.next();
    if (item.qualify()) {
        item.operate();
        i.remove();
    }
}
117
Paul Boddington

多くのアプローチがあります。 myList.remove(element)を使用する場合、equals()をオーバーライドする必要があります。私が好むのは:

allList.removeIf(item -> item.getId().equals(elementToDelete.getId()));

幸運と幸せなコーディング:)

4

1行ではありませんが、partitioningByコレクターを使用できます。

Map<Boolean, Set<Item>> map = 
    set.stream()
       .collect(partitioningBy(Item::qualify, toSet()));

map.get(true).forEach(i -> ((Qualifier)i).operate());
set = map.get(false);

セットを2回繰り返すので、ストリームをフィルタリングするためと、対応する要素を削除するために1回繰り返すので、より効率的です。

そうでなければ、あなたのアプローチは比較的良いと思います。

4
Alexis C.

本当にしたいことは、セットをパーティション分割することです。残念ながらJava 8では、端末の "collect"メソッドを介してのみパーティショニングが可能です。次のような結果になります。

// test data set
Set<Integer> set = ImmutableSet.of(1, 2, 3, 4, 5);
// predicate separating even and odd numbers
Predicate<Integer> evenNumber = n -> n % 2 == 0;

// initial set partitioned by the predicate
Map<Boolean, List<Integer>> partitioned = set.stream().collect(Collectors.partitioningBy(evenNumber));

// print even numbers
partitioned.get(true).forEach(System.out::println);
// do something else with the rest of the set (odd numbers)
doSomethingElse(partitioned.get(false))

更新しました:

上記のコードのScalaバージョン

val set = Set(1, 2, 3, 4, 5)
val partitioned = set.partition(_ % 2 == 0)
partitioned._1.foreach(println)
doSomethingElse(partitioned._2)`
2

操作後、それらはそれ以上の目的を果たさず、元のセットから削除する必要があります。コードはうまく機能しますが、Streamにこれを行う操作が1行であるという感覚を揺るがすことはできません。

ストリームを使用してストリームのソースから要素を削除することはできません。 Javadoc から:

ほとんどのストリーム操作は、ユーザー指定の動作を記述するパラメータを受け入れます。正しい動作を維持するために、これらの動作パラメータは次のとおりです。

  • 非干渉である必要があります(ストリームソースを変更しません);そして
  • ほとんどの場合、ステートレスである必要があります(その結果は、ストリームパイプラインの実行中に変化する可能性のある状態に依存してはなりません)。
2
user7502825

あなたの質問を正しく理解したら:

set = set.stream().filter(item -> {
    if (item.qualify()) {
        ((Qualifier) item).operate();
        return false;
    }
    return true;
}).collect(Collectors.toSet());
1
user_3380739

いいえ、実装はおそらく最も単純なものです。 removeIf述部の状態を変更することにより、非常に悪いことをするかもしれませんが、しないでください。一方、実際にはイテレータベースの命令型実装に切り替えることは合理的かもしれませんが、実際にはこのユースケースにより適切で効率的です。

1
Louis Wasserman

最上位の回答に記載されている、ストリームを使用するときのポールの明確な懸念がわかります。おそらく、説明変数を追加すると、意図が少し明確になります。

set.removeIf(item -> {
  boolean removeItem=item.qualify();
  if (removeItem){
    item.operate();
  }
  return removeItem;
});
0
vacant78