ステートレスラムダまたはステートフルラムダの場合の同じ呼び出しサイトの頻繁な実行と、(異なる呼び出しサイトによる)同じメソッドへのメソッド参照の頻繁な使用を区別する必要があります。
次の例を見てください。
Runnable r1=null;
for(int i=0; i<2; i++) {
Runnable r2=System::gc;
if(r1==null) r1=r2;
else System.out.println(r1==r2? "shared": "unshared");
}
ここでは、同じ呼び出しサイトが2回実行され、ステートレスラムダが生成され、現在の実装は"shared"
。を出力します。
Runnable r1=null;
for(int i=0; i<2; i++) {
Runnable r2=Runtime.getRuntime()::gc;
if(r1==null) r1=r2;
else {
System.out.println(r1==r2? "shared": "unshared");
System.out.println(
r1.getClass()==r2.getClass()? "shared class": "unshared class");
}
}
この2番目の例では、同じ呼び出しサイトが2回実行され、Runtime
インスタンスへの参照を含むラムダが生成され、現在の実装は出力されます"unshared"
が"shared class"
。
Runnable r1=System::gc, r2=System::gc;
System.out.println(r1==r2? "shared": "unshared");
System.out.println(
r1.getClass()==r2.getClass()? "shared class": "unshared class");
対照的に、最後の例では、同等のメソッド参照を生成する2つの異なる呼び出しサイトがありますが、1.8.0_05
それ以降はと"unshared"
を出力し"unshared class"
ます。
ラムダ式またはメソッド参照ごとに、コンパイラーはinvokedynamic
、クラス内のJRE提供のブートストラップメソッドLambdaMetafactory
と、目的のラムダ実装クラスを生成するために必要な静的引数を参照する命令を発行します。メタファクトリが生成するのは実際のJREに任されてinvokedynamic
いCallSite
ますが、最初の呼び出しで作成されたインスタンスを記憶して再利用するのは、命令の指定された動作です。
現在のJREは、ステートレスラムダの定数オブジェクトをConstantCallSite
含むを生成しMethodHandle
ます(これを別の方法で行う理由は考えられません)。また、メソッドへのメソッド参照static
は常にステートレスです。したがって、ステートレスラムダとシングルコールサイトの場合、答えは次のようになります。キャッシュしないでください。JVMはキャッシュします。キャッシュしない場合は、打ち消すべきではないという強い理由が必要です。
パラメータthis::func
を持ち、this
インスタンスへの参照を持つラムダの場合、状況は少し異なります。JREはそれらをキャッシュできますが、これはMap
、実際のパラメーター値と結果のラムダの間で何らかの維持を行うことを意味し、単純な構造化ラムダインスタンスを再度作成するよりもコストがかかる可能性があります。現在のJREは、状態を持つラムダインスタンスをキャッシュしません。
しかし、これはラムダクラスが毎回作成されるという意味ではありません。これは、解決された呼び出しサイトが、最初の呼び出しで生成されたラムダクラスをインスタンス化する通常のオブジェクト構築のように動作することを意味します。
同様のことが、異なる呼び出しサイトによって作成された同じターゲットメソッドへのメソッド参照にも当てはまります。JREはそれらの間で単一のラムダインスタンスを共有することが許可されていますが、現在のバージョンでは共有できません。おそらく、キャッシュのメンテナンスが効果を発揮するかどうかが明確でないためです。ここでは、生成されたクラスでさえ異なる場合があります。
したがって、例のようにキャッシュすると、プログラムが実行しない場合とは異なる処理を実行する可能性があります。しかし、必ずしもより効率的であるとは限りません。キャッシュされたオブジェクトは、一時的なオブジェクトよりも常に効率的であるとは限りません。ラムダの作成によって引き起こされるパフォーマンスへの影響を実際に測定しない限り、キャッシュを追加しないでください。
キャッシュが役立つ可能性がある特別なケースはいくつかあると思います。
- 同じ方法を参照している多くの異なるコールサイトについて話している
- ラムダはコンストラクター/クラスの初期化で作成されます。これは、後で使用サイトで作成されるためです。
- 複数のスレッドから同時に呼び出される
- 最初の呼び出しのパフォーマンスが低下する