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オプティマイザは、ループバリアントと同じように、ストリームバリアントに対してまったく同じ最適化されたネイティブコードを生成します。
私はラムダがクラスではなく合成メソッドにコンパイルされていると考えました(「ラムダ式用に生成されたクラス」を参照)。どちらが正しい? – erickson
詳細な回答をありがとうございました。私は2人が同等のパフォーマンスであることを期待していました。私の前提が真実であれば、私は確信できませんでした。それはそうだったようですが、今は理由をより深く理解しています。ありがとうございました! – Chippen
@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