2016-04-13 24 views
2

私が知る限り、2つのフィルタを持つストリームがある場合、それらは& &のバイトコードと結合されます。私%2 == 0 & &私%3 == 0java stream.peek()はどのようにバイトコードに影響しますか?

がかいま見この影響を与えないように

IntStream.range(1,10) 
    .filter(i -> i % 2 == 0) 
    .filter(i -> i % 3 == 0) 
    .sum(); 

例えば

は何でしょうか?

最初のファイラーの後にピークを表示すると、2468が得られ、2番目のピーク後にピークを表示すると、6(もちろん)になります。

しかし、あなたは両方の場所

IntStream.range(1,10) 
      .filter(integer -> integer % 2 == 0) 
      .peek(i-> System.out.print(i)) 
      .filter(integer -> integer % 3 == 0) 
      .peek(i-> System.out.print(i)) 
      .sum(); 

覗く場合は24668.

私の仮定は、この操作は何とかPEEK呼び出しの結果として分離されていることを意味しなければならないことになってしまいます。何かのように

これは本当ですか、そうであればパフォーマンスに影響します(私はそうではないと思われます)。

答えて

5

Stream APIはordinary Java APIです(自分で確認できます)。 It’s filter methodは、任意のPredicateインスタンスを受信します。ラムダ式または通常のclass(またはすべての可能性を示すためにenum)を使用して実装します。あなたはその後filter 2回起動した場合

、基本的な実装Predicate.andを呼び出すことにより、単一のフィルタにそれらを結合するが、それがないかどうかラムダ式を介して実行述語の場合には影響を与えませんでした。 andメソッドをオーバーライドして、彼らは二Predicate実装を認識した場合に最適化されたものを提供することができ

Predicateカスタムとは異なり実装、ラムダ式のために生成されたクラスは、任意のdefaultメソッドをオーバーライドしていないが、ちょうど1つのabstract関数法、ここではPredicate.testなので、andを呼び出すと、defaultメソッドが返すものが得られます。を使用しないStream実装のように、両方のソース述語への参照を保持し、それらを結合する新しいPredicateが得られます。

これらの実装には大きな違いはなく、Consumerのように別のアクションを挿入すると、その間にpeekが渡されます。もちろん、これではこれ以上の処理は行われないため、にはというパフォーマンスの影響がありますが、述語に関する影響はありません。

しかし、あなたの一般的な誤解は、あなたは大きな違いがあったと思うことのようだ:

for(int i=1; i<10; i++) { 
    if(i%2==0 && i%3==0) 
     System.out.print(i); 
} 

for(int i=1; i<10; i++) { 
    if(i%2==0) { 
     System.out.print(i); 
     if(i%3==0) 
      System.out.print(i); 
    } 
} 

がコンパイルされたメソッドのバイトコードを見てください:

// first variant   second variant 
    0: iconst_1    0: iconst_1 
    1: istore_1    1: istore_1 
    2: iload_1    2: iload_1 
    3: bipush  10  3: bipush  10 
    5: if_icmpge  33  5: if_icmpge  40 
    8: iload_1    8: iload_1 
    9: iconst_2    9: iconst_2 
10: irem     10: irem 
11: ifne   27  11: ifne   34 
          14: getstatic  #2 // Field java/lang/System.out:Ljava/io/PrintStream; 
          17: iload_1 
          18: invokevirtual #3 // Method java/io/PrintStream.print:(I)V 
14: iload_1    21: iload_1 
15: iconst_3    22: iconst_3 
16: irem     23: irem 
17: ifne   27  24: ifne   34 
20: getstatic  #2  27: getstatic  #2 // Field java/lang/System.out:Ljava/io/PrintStream; 
23: iload_1    30: iload_1 
24: invokevirtual #3  31: invokevirtual #3 // Method java/io/PrintStream.print:(I)V 
27: iinc   1, 1 34: iinc   1, 1 
30: goto   2  37: goto   2 
33: return    40: return 

ご覧のとおり、printステートメントを挿入すると、正確にはinsertio印刷文のn、それ以上は何もない。つまり、&&演算子は、2つのネストされたifステートメントとは異なる魔法の融合物ではありません。どちらもまったく同じ意味で、バイトコードで行います。

Stream APIの使用方法も同じですが、条件式がPredicateインスタンスで表され、挿入された文がConsumerであるため、コードは複雑になります。しかし、最善のケースでは、HotSpotオプティマイザは、ループバリアントと同じように、ストリームバリアントに対してまったく同じ最適化されたネイティブコードを生成します。

+0

私はラムダがクラスではなく合成メソッドにコンパイルされていると考えました(「ラムダ式用に生成されたクラス」を参照)。どちらが正しい? – erickson

+0

詳細な回答をありがとうございました。私は2人が同等のパフォーマンスであることを期待していました。私の前提が真実であれば、私は確信できませんでした。それはそうだったようですが、今は理由をより深く理解しています。ありがとうございました! – Chippen

+2

@erickson:ラムダ式は、JREが関数インタフェースを満たし、その合成メソッドを呼び出すクラスを生成する合成メソッドに*コンパイルされます。生成されたクラスの多くのプロパティは意図的に指定されていませんが、 'default'メソッドをオーバーライドしないという事実は[JLS§15.27.4で修正されています](https://docs.oracle.com/javase/specs/ jls/se8/html/jls-15.html#jls-15.27.4-300-C): "*クラスは、上記の対象となる機能的インタフェースタイプまたは他のインタフェースタイプの他のメソッドをオーバーライドします。 'Object'クラス*" – Holger

0

バイトコードレベルでは最適化されていません。各ラムダは別個の方法です。 Javaは、実行時にすべてを透過的に最適化するためにJVMに依存しています。

関連する問題