2016-08-04 8 views
4

限られた数の値をセットにストリーミングしようとしていますが、制限を適用する前に新しい要素であることを確認する必要があります。たとえば:add()は両方それがコレクションに新しいのかどうかの要素レポートを追加することができますので、シーケンシャルストリーム操作に副作用がありますか?

Set<Integer> destination = ... 
Set<Integer> source = ... 
source.stream() 
     .filter(i -> !destination.contains(i)) 
     .limit(10) 
     .forEach(destination::add); 

しかし、冗長チェックは、私を悩ませています。だから私はこれを行うことを考えていた:

source.stream() 
     .filter(destination::add) 
     .limit(10) 
     .forEach(i -> {}); // no-op terminal operation to force evaluation 

がハック端末操作を無視すると、一般的に推奨されて副作用、とフィルタ操作を使用しての問題があります。私は、並列ストリームで副作用を伴うmap()filter()を使用することが安全でない理由を理解しています。私の質問は、この場合のように連続したストリームで受け入れられるだろうか?そうでない場合は、どうしてですか?

答えて

7

副作用と順次ストリームには基本的な問題はありませんが、ストリームAPIは各要素が各要素で実行されることを保証しないため、上記の2番目の実装は無効です

2番目の実装では、制限が適用される前に、10個以上の要素がdestinationに追加されます。あなたのノー・フォー・フォーには10枚しか見えませんが、あなたはセットでさらに多くなることがあります。

ストリームに加えて、javaにはforwhileのようなループ構造があり、このようなことを簡単に表現できます。

あなた必見利用の流れならば、あなたはこのようにそれを行うことができます。述語がfalseを返すよう

int maxSize = destination.size()+10; 
source.stream().allMatch(x -> destination.size()<maxsize && (destination.add(x)||true)); 

allMatchとすぐに反復を停止します。

+2

あなたは私のシナリオを誤解していると思います。 'source'はセットなので、重複する要素は含まれません。私は 'destination 'には存在しない' source'の要素を追加しようとしています。だから私は最初の解決策はまだ動作すると思います。しかし、あなたの全体的なポイントは興味深いものです。つまり、ストリーム要素がパイプライン全体を一度に通過しない可能性があります。あなたはそのための情報源を持っていますか、あるいはそれとは反対にドキュメンテーションの欠如に頼っていますか? – shmosel

+0

逆に、ストリームの多くの最適化がチャンク内の要素を処理するという事実は、*と*の記載が不足しています。あなたが書いたコードは今日はうまくいくようですが、新しいJavaのバージョンや他の実装ではうまく変わる可能性のあるものに依存しています。 –

+0

私は実際に小切手の目的を誤解しました。それに応じて回答を編集しました。 –

-1

があなたのためにこの作業を行います。

このクラスウィル印刷実行
package be.objectsmith; 

import java.util.Arrays; 
import java.util.HashSet; 
import java.util.Set; 
import java.util.stream.Collectors; 
import java.util.stream.IntStream; 

public class Playground { 
    public static void main(String[] args) { 
     copy(
      IntStream.range(1, 20).boxed().collect(Collectors.toSet()), 
      new HashSet<>(Arrays.asList(2, 5))); 
     copy(
      IntStream.range(1, 5).boxed().collect(Collectors.toSet()), 
      new HashSet<>(Arrays.asList(2, 5))); 
    } 

    private static void copy(Set<Integer> source, Set<Integer> destination) { 
     source 
      .stream() 
      .map(destination::add) 
      .filter(resultOfAdding -> resultOfAdding) 
      .limit(10) 
      .collect(Collectors.toList()); // Need a terminal operation 
     System.out.println("source = " + source); 
     System.out.println("destination = " + destination); 
    } 

} 

:あなたが見ることができるように

source = [1, 2, 3, 4] 
destination = [1, 2, 3, 4, 5] 
source = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19] 
destination = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] 

を、それだけで10個の要素を追加しました。 2つ目の呼び出しは、追加する要素が10個未満の場合にも機能することを示しています。

+1

これは、2つの操作にフィルタ(destination :: add)を分割する、2番目の実装のちょっと複雑なバージョンです。 –

+0

@MattTimmermansこれは2番目の実装を修正したもので、OPが要求するものと全く同じです: 'Set.add()'自身が新しい要素であるかどうかを報告するという事実を利用します。次に、成功した追加を数えて制限するだけです。私は '.count()'で端末操作を置き換えることさえできます。すべて私はまだ最初の実装を好むでしょう。 'add'は成功を報告するという便利さがあり、いくつかのケースをよりエレガントに表現することができますが、ここでは不便さが増えています。 – bowmore

+0

@bowmore何が修正されますか?オリジナルは、指定されたテストケースでも動作し、Set :: addからの戻り値を使用し、この答えにも問題はありません。 –

関連する問題