Sie müssen zwischen häufigen Ausführungen derselben Aufrufstelle für zustandsloses Lambda oder zustandsbehaftete Lambdas und der häufigen Verwendung einer Methodenreferenz auf dieselbe Methode (durch verschiedene Aufrufstellen) unterscheiden.
Schauen Sie sich die folgenden Beispiele an:
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");
}
Hier wird dieselbe Aufrufstelle zweimal ausgeführt, wodurch ein zustandsloses Lambda erzeugt wird und die aktuelle Implementierung gedruckt wird "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");
}
}
In diesem zweiten Beispiel wird der gleiche Anruf Ort zweimal ausgeführt wird , eine Lambda enthält einen Verweis auf eine Herstellung Runtime
druckt Instanz und die aktuellen Implementierung "unshared"
aber "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");
Im letzten Beispiel sind im Gegensatz dazu zwei verschiedene Aufrufstellen, die eine äquivalente Methodenreferenz erzeugen, aber ab 1.8.0_05
diesem Zeitpunkt werden "unshared"
und gedruckt "unshared class"
.
Für jeden Lambda-Ausdruck oder jede Methodenreferenz gibt der Compiler eine invokedynamic
Anweisung aus, die auf eine von JRE bereitgestellte Bootstrap-Methode in der Klasse LambdaMetafactory
und die statischen Argumente verweist, die zum Erzeugen der gewünschten Lambda-Implementierungsklasse erforderlich sind. Es bleibt der tatsächlichen JRE überlassen, was die Meta-Factory erzeugt, aber es ist ein bestimmtes Verhalten der invokedynamic
Anweisung, CallSite
die beim ersten Aufruf erstellte Instanz zu speichern und wiederzuverwenden .
Die aktuelle JRE erzeugt ein ConstantCallSite
a enthält , MethodHandle
auf ein konstantes Objekt für staatenlos lambdas (und es gibt keinen denkbaren Grund , es anders zu tun). Und Methodenverweise auf static
Methoden sind immer zustandslos. Für zustandslose Lambdas und einzelne Call-Sites muss die Antwort lauten: Nicht zwischenspeichern, die JVM wird es tun, und wenn dies nicht der Fall ist, muss es starke Gründe geben, denen Sie nicht entgegenwirken sollten.
Bei Lambdas mit Parametern und this::func
einem Lambda, das auf die this
Instanz verweist, sind die Dinge etwas anders. Die JRE darf sie zwischenspeichern, dies würde jedoch bedeuten, dass eine Art Map
zwischen den tatsächlichen Parameterwerten und dem resultierenden Lambda beibehalten wird, was teurer sein könnte, als nur diese einfache strukturierte Lambda-Instanz erneut zu erstellen. Die aktuelle JRE speichert keine Lambda-Instanzen mit einem Status zwischen.
Dies bedeutet jedoch nicht, dass die Lambda-Klasse jedes Mal erstellt wird. Dies bedeutet lediglich, dass sich die aufgelöste Aufrufstelle wie eine gewöhnliche Objektkonstruktion verhält, die die Lambda-Klasse instanziiert, die beim ersten Aufruf generiert wurde.
Ähnliches gilt für Methodenreferenzen auf dieselbe Zielmethode, die von verschiedenen Aufrufstellen erstellt wurden. Die JRE darf eine einzelne Lambda-Instanz zwischen ihnen teilen, in der aktuellen Version jedoch nicht, höchstwahrscheinlich, weil nicht klar ist, ob sich die Cache-Wartung auszahlt. Hier können sich sogar die generierten Klassen unterscheiden.
Beim Caching wie in Ihrem Beispiel kann Ihr Programm also andere Aufgaben ausführen als ohne. Aber nicht unbedingt effizienter. Ein zwischengespeichertes Objekt ist nicht immer effizienter als ein temporäres Objekt. Sofern Sie nicht wirklich die Auswirkungen einer Lambda-Erstellung auf die Leistung messen, sollten Sie kein Caching hinzufügen.
Ich denke, es gibt nur einige Sonderfälle, in denen Caching nützlich sein könnte:
- Wir sprechen über viele verschiedene Anrufstellen, die sich auf dieselbe Methode beziehen
- Das Lambda wird in der Konstruktor- / Klasseninitialisierung erstellt, da dies später auf der Verwendungsseite erfolgt
- von mehreren Threads gleichzeitig aufgerufen werden
- leiden unter der geringeren Leistung des ersten Aufrufs