web-dev-qa-db-ja.com

Java 8でラムダ構文の代わりにメソッド参照構文を使用することには、パフォーマンス上の利点がありますか?

メソッド参照はラムダラッパーのオーバーヘッドをスキップしますか?彼らは将来になるのでしょうか?

メソッド参照に関するJavaチュートリアル によると:

時々...ラムダ式は既存のメソッドを呼び出すだけです。そのような場合、既存のメソッドを名前で参照する方がわかりやすいことがよくあります。メソッド参照を使用すると、これを行うことができます。これらは、すでに名前が付いているメソッド用のコンパクトで読みやすいラムダ式です。

いくつかの理由から、メソッド参照構文よりもラムダ構文の方を好みます。

ラムダはより明確です

Oracleの主張にも関わらず、メソッド参照構文があいまいなので、ラムダ構文はオブジェクトメソッド参照の省略形より読みやすいと思います。

Bar::foo

Xのクラスで静的な1つの引数を持つメソッドを呼び出して、xに渡しますか?

x -> Bar.foo(x)

または、xで引数なしのインスタンスメソッドを呼び出していますか?

x -> x.foo()

メソッド参照構文は、どちらか一方の代わりになります。それはあなたのコードが実際に何をしているかを隠します。

ラムダはより安全です

Bar :: fooをクラスメソッドとして参照し、後でBarが同じ名前のインスタンスメソッドを追加した場合(またはその逆)、コードはコンパイルされなくなります。

ラムダを一貫して使用できます

ラムダで任意の関数をラップできます。これにより、どこでも同じ構文を一貫して使用できます。メソッド参照構文は、プリミティブ配列を取得または返すメソッド、チェックされた例外をスローするメソッド、またはインスタンスと静的メソッドとして同じメソッド名を使用するメソッドでは機能しません(メソッド参照構文は、どのメソッドが呼び出されるかが不明確であるため) 。同じ数の引数を持つオーバーロードされたメソッドがある場合、これらは機能しませんが、とにかくそれを行うべきではないので(Josh Blochの項目41を参照)、メソッド参照に対してそれを保持できません。

結論

パフォーマンスが低下しない場合は、IDEで警告をオフにし、時々メソッドリファレンスをコードに振りかけることなくラムダ構文を一貫して使用したいと思っています。

追伸.

ここにもそこにもありませんが、私の夢では、オブジェクトメソッド参照は次のようになり、lambdaラッパーなしでオブジェクトのメソッドに対して直接invoke-dynamicを適用します。

_.foo()
61
GlenPeterson

多くのシナリオでは、ラムダとメソッド参照は同等だと思います。ただし、ラムダは、宣言するインターフェイスタイプによって呼び出しターゲットをラップします。

例えば

_public class InvokeTest {

    private static void invoke(final Runnable r) {
        r.run();
    }

    private static void target() {
        new Exception().printStackTrace();
    }

    @Test
    public void lambda() throws Exception {
        invoke(() -> target());
    }

    @Test
    public void methodReference() throws Exception {
        invoke(InvokeTest::target);
    }
}
_

コンソールにスタックトレースが出力されます。

lambda()では、target()を呼び出すメソッドはlambda$lambda$0(InvokeTest.Java:20)であり、追跡可能な行情報があります。明らかに、それはあなたが書くラムダであり、コンパイラーは匿名メソッドを生成します。そして、ラムダメソッドの呼び出し元はInvokeTest$$Lambda$2/1617791695.run(Unknown Source)のようなものです。つまり、JVMのinvokedynamic呼び出しです。これは、呼び出しがリンクされていることを意味します生成されたメソッドに

methodReference()では、target()を呼び出すメソッドは直接InvokeTest$$Lambda$1/758529971.run(Unknown Source)であり、呼び出しが直接_InvokeTest::target_メソッド

結論

とりわけ、メソッド参照と比較して、ラムダ式を使用すると、ラムダから生成メソッドへのメソッド呼び出しが1つ増えるだけです。

15
JasonMing

metafactory がすべてです

まず、mostメソッド参照は、ラムダメタファクトリによるデシュガリングを必要とせず、単に参照メソッドとして使用されます。 Translation of Lambda Expressions ( "TLE")記事の「ラムダボディシュガー」セクション:

すべてが等しい場合、プライベートメソッドはインスタンスメソッドよりもプライベートではない静的メソッドよりも優先されます。ラムダ本体がラムダ式が表示される最も内側のクラスで脱糖される場合、署名はラムダの本体署名と一致する必要があります。引数は、キャプチャされた値の引数リストの前に追加する必要があり、メソッド参照をまったく削除しません。ただし、このベースライン戦略から逸脱する必要がある例外的なケースがあります。

これは、TLEの「The Lambda Metafactory」でさらに強調されています。

metaFactory(MethodHandles.Lookup caller, // provided by VM
            String invokedName,          // provided by VM
            MethodType invokedType,      // provided by VM
            MethodHandle descriptor,     // lambda descriptor
            MethodHandle impl)           // lambda body

impl引数は、ラムダメソッド(脱糖ラムダ本体またはメソッド参照で指定されたメソッド)を識別します。

静的(Integer::sum)または無制限のインスタンスメソッド(Integer::intValue)参照は、「高速パス」によって最適に処理できるという意味で、「最も単純」または最も「便利」です メタファクトリバリアント脱糖なし。この利点は、TLEの「メタファクトリバリアント」で効果的に指摘されています。

必要のない場所で引数を排除することにより、クラスファイルは小さくなります。また、高速パスオプションは、VMのバーを下げてラムダ変換操作を組み込み、 "ボックス化"操作として扱い、ボックス化解除の最適化を容易にします。

当然、インスタンスキャプチャメソッドの参照(obj::myMethod)は、呼び出し用のメソッドハンドルへの引数として制限付きインスタンスを提供する必要があります。これは、「bridge」メソッドを使用してdesugaringする必要があることを意味します。

結論

あなたが示唆しているラムダ「ラッパー」が何であるかは正確にはわかりませんが、ユーザー定義のラムダまたはメソッド参照を使用した最終的な結果は同じですが、way到達したことはかなり異なるようであり、それが今の状況でない場合、将来的に異なる可能性があります。したがって、メタファクトリーによってメソッド参照をより最適な方法で処理できる可能性が高いと思います。

31
h.j.k.

パフォーマンスに影響を与える可能性のあるラムダ式を使用すると、非常に重大な結果が1つあります。

ラムダ式を宣言すると、ローカルスコープ上にclosureが作成されます。

これは何を意味し、パフォーマンスにどのように影響しますか?

さて、あなたが尋ねてうれしいです。これは、それらの小さなラムダ式のそれぞれが小さな匿名の内部クラスであり、ラムダ式の同じスコープ内にあるすべての変数への参照を一緒に運ぶことを意味します。

これは、オブジェクトインスタンスとそのすべてのフィールドのthis参照も意味します。ラムダの実際の呼び出しのタイミングによっては、ラムダを保持しているオブジェクトがまだ存続している限り、ガベージコレクターがこれらの参照を手放すことができないため、かなりのリソースリークになる可能性があります...

0
Roland Tepp