web-dev-qa-db-ja.com

foreachメソッドでストリームからオブジェクトを削除する方法は?

私は配列する必要があります:arrAarrBarrAarrBは異なるタイプのオブジェクトのリストであり、add関数はオブジェクトAをオブジェクトBに変換します。各オブジェクトをarrAからarrBに追加し、そのオブジェクトをarrAから削除します。ストリームでこれをしようとしています:

_arrA.stream().foreach(c -> {arrB.add(c); arrA.remove(c);});
_

これを実行すると、次の2つのことが起こります。

  1. すべてのオブジェクトがarrAからarrBに渡されるわけではありません。
  2. 数回の反復の後、nullポインター例外がスローされます。

remove()呼び出しのたびに配列の長さが減り、反復のカウンターが増えるためだと思います(奇数インデックスの下のオブジェクトのみがarrBに渡されます)

今、私は1回のストリーム呼び出しで配列をコピーしてから2番目のストリーム呼び出しでオブジェクトを削除することでこれを解決できましたが、これは私にとっては正しくないようです。

この問題の適切な解決策は何ですか?

編集。追加情報:実際の実装では、以前にフィルタリングされた場合、このリスト

_arrA.stream().filter(some condition).foreach(c -> {arrB.add(c); arrA.remove(c);});
_

異なる条件に一致する要素を異なるリストに追加するために数回呼び出されます(_arrC, arrD_など)が、各オブジェクトは1つのリストにしか存在できません

8
Akka Jaworek

ストリームは、より機能的な方法で使用するように設計されており、できればコレクションを不変として扱います。

ストリーム以外の方法は次のとおりです。

arrB.addAll(arrA);
arrA.clear();

ただし、Streamsを使用している可能性があるため、入力をフィルタリングして、次のようにすることができます。

arrB.addAll(arrA.stream().filter(x -> whatever).toList())

次に、arrAから削除します(コメントについて@Holgarに感謝します)。

arrA.removeIf(x -> whatever)

述部が高価な場合は、次のように分割できます。

Map<Boolean, XXX> lists = arrA.stream()
  .collect(Collectors.partitioningBy(x -> whatever));
arrA = lists.get(false);
arrB = lists.get(true);

または変更のリストを作成します。

List<XXX> toMove = arrA.stream().filter(x->whatever).toList();
arrA.removeAll(toMove);
arrB.addAll(toMove);
15
davidsheldon

他の人が述べたように、これはforeachでは不可能です-for (A a: arrA)ループでは要素を削除できないためです。

私の意見では、最もきれいな解決策は、イテレータでwhileにプレーンを使用することです-イテレータを使用すると、反復処理中に要素を削除できます(コレクションがサポートしている限り)。

Iterator<A> it = arrA.iterator()
while (it.hasNext()) {
    A a = it.next();
    if (!check(a))
        continue;
    arrB.add(a);
    it.remove();
}

これにより、arrAをコピー/クローニングする必要もなくなります。

4
Martin Nyolt

繰り返し処理中にarrAから削除できるとは思わない。

これを回避するには、新しいArrayList <>()でラップします。

new ArrayList <>(arrA).stream()。foreach(c-> {arrB.add(c); arrA.remove(c);});

1
Mike Samaras

各remove()呼び出しの後に配列の長さが減少し、反復のカウンターが増加するためだと思います

正しい。 for-each-loopは通常のfor-loopに似ていますが、読み書きが簡単です。あなたはそれを構文糖と考えることができます。内部的には、イテレータまたは配列インデックスを使用します。ストリームのforEachメソッドは、より洗練されたバージョンであり、並列実行と機能的なコーディングスタイルを可能にします ただし、独自の欠点があります

インデックス付きループと同様に、ループ中に要素を削除するとループが中断します。インデックス0、1、および2の3つの要素を持つことを検討してください。最初の反復で要素0を削除すると、リスト項目は1つ上に移動し、次の反復では要素0(以前1)および1(以前2)になります。ループ変数は1を指すようになったため、実際に次の項目をスキップします。インデックス2に到達すると、作業中のループには1つのアイテムしか残っていません(2つ削除しました)。インデックスが範囲外であるため、エラーがスローされます。

可能な解決策:

  • リストの複製と消去には、Listメソッドを使用します。
  • 個々のアイテムごとにメソッドを呼び出す必要がある場合は、2つのループで実行してください。
1