ステートレス ラムダまたはステートフル ラムダに対して同じcall-siteを頻繁に実行することと、同じメソッドへのメソッド参照を (異なる呼び出しサイトによって) 頻繁に使用することを区別する必要があります。
次の例を見てください。
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");
}
ここでは、同じ call-site が 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");
}
}
Runtime
この 2 番目の例では、同じ call-site が 2 回実行され、インスタンスへの参照を含むラムダが生成され、現在の実装では"unshared"
but が出力され"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"
andが出力され"unshared class"
ます。
ラムダ式またはメソッド参照ごとに、コンパイラはinvokedynamic
、クラス内の JRE 提供のブートストラップ メソッドLambdaMetafactory
と、目的のラムダ実装クラスを生成するために必要な静的引数を参照する命令を発行します。メタ ファクトリが生成するものは実際の JRE に委ねられますが、最初の呼び出しで作成されたインスタンスinvokedynamic
を記憶して再利用するのは、命令の指定された動作です。CallSite
現在の JRE は、ステートレス ラムダの定数オブジェクトへのConstantCallSite
格納を生成します (これを別の方法で行う理由は考えられません)。MethodHandle
また、メソッドへのメソッド参照static
は常にステートレスです。したがって、ステートレスなラムダと単一の呼び出しサイトの場合、答えは次のとおりです。キャッシュしないでください。JVM が行います。そうでない場合は、対抗してはならないという強い理由が必要です。
パラメーターを持つthis::func
ラムダで、インスタンスへの参照を持つラムダのthis
場合、状況は少し異なります。JRE はそれらをキャッシュすることができますが、これは、実際のパラメーター値と結果のラムダの間に何らかの種類を維持することを意味しMap
、その単純な構造化されたラムダ インスタンスを再度作成するよりもコストがかかる可能性があります。現在の JRE は、状態を持つラムダ インスタンスをキャッシュしません。
ただし、これはラムダ クラスが毎回作成されるという意味ではありません。これは、解決された呼び出しサイトが、最初の呼び出しで生成されたラムダ クラスをインスタンス化する通常のオブジェクト構築のように動作することを意味します。
同様のことが、異なる呼び出しサイトによって作成された同じターゲット メソッドへのメソッド参照にも当てはまります。JRE はそれらの間で 1 つのラムダ インスタンスを共有できますが、現在のバージョンでは共有できません。これはおそらく、キャッシュのメンテナンスが報われるかどうかが明確でないためです。ここでは、生成されたクラスでさえ異なる場合があります。
したがって、あなたの例のようなキャッシングは、あなたのプログラムがそうでない場合とは異なることをするかもしれません。しかし、必ずしもより効率的であるとは限りません。キャッシュされたオブジェクトは、一時オブジェクトより常に効率的であるとは限りません。ラムダの作成によるパフォーマンスへの影響を実際に測定しない限り、キャッシュを追加しないでください。
キャッシングが役立つ特別なケースがいくつかあると思います。
- 同じメソッドを参照する多くの異なる呼び出しサイトについて話している
- ラムダはコンストラクター/クラスの初期化で作成されます。これは、後で使用サイトで
- 複数のスレッドから同時に呼び出される
- 最初の呼び出しのパフォーマンスの低下に苦しむ