Debe hacer una distinción entre ejecuciones frecuentes del mismo sitio de llamada , para lambda sin estado o lambdas con estado, y usos frecuentes de una referencia de método al mismo método (por diferentes sitios de llamada).
Mira los siguientes ejemplos:
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");
}
Aquí, el mismo sitio de llamada se ejecuta dos veces, produciendo una lambda sin estado y se imprimirá la implementación actual "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");
}
}
En este segundo ejemplo, el mismo sitio de llamada se ejecuta dos veces, produciendo una lambda que contiene una referencia a un Runtime
instancia y la implementación actual imprimirá "unshared"
pero "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");
Por el contrario, en el último ejemplo hay dos sitios de llamadas diferentes que producen una referencia de método equivalente, pero a partir de 1.8.0_05
este se imprimirán "unshared"
y "unshared class"
.
Para cada expresión lambda o referencia de método, el compilador emitirá una invokedynamic
instrucción que se refiere a un método bootstrap proporcionado por JRE en la clase LambdaMetafactory
y los argumentos estáticos necesarios para producir la clase de implementación lambda deseada. Se deja al JRE real lo que produce la metafábrica, pero es un comportamiento específico de la invokedynamic
instrucción recordar y reutilizar la CallSite
instancia creada en la primera invocación.
El JRE actual produce un ConstantCallSite
contiene una MethodHandle
a una constante para lambdas sin estado (y no hay ninguna razón imaginable para hacerlo de manera diferente). Y referencias de métodos astatic
método método siempre son apátridas. Entonces, para lambdas sin estado y sitios de llamada única, la respuesta debe ser: no almacenar en caché, la JVM funcionará y, si no lo hace, debe tener fuertes razones por las que no debe contrarrestar.
Para lambdas que tienen parámetros, y this::func
es una lambda que tiene una referencia a la this
instancia, las cosas son un poco diferentes. El JRE puede almacenarlos en caché, pero esto implicaría mantener algún tipo de valor Map
entre los valores reales de los parámetros y la lambda resultante, lo que podría ser más costoso que simplemente crear de nuevo esa instancia lambda estructurada simple. El JRE actual no almacena en caché las instancias lambda que tienen un estado.
Pero esto no significa que la clase lambda se cree siempre. Simplemente significa que el sitio de llamada resuelto se comportará como una construcción de objeto ordinaria que instancia la clase lambda que se ha generado en la primera invocación.
Se aplican cosas similares a las referencias de métodos al mismo método de destino creado por diferentes sitios de llamada. El JRE puede compartir una única instancia lambda entre ellos, pero en la versión actual no lo hace, probablemente porque no está claro si el mantenimiento de la caché dará sus frutos. Aquí, incluso las clases generadas pueden diferir.
Por lo tanto, el almacenamiento en caché como en su ejemplo podría hacer que su programa haga cosas diferentes que sin él. Pero no necesariamente más eficiente. Un objeto en caché no siempre es más eficiente que un objeto temporal. A menos que realmente mida un impacto en el rendimiento causado por una creación de lambda, no debe agregar ningún almacenamiento en caché.
Creo que solo hay algunos casos especiales en los que el almacenamiento en caché podría ser útil:
- estamos hablando de muchos sitios de llamadas diferentes que se refieren al mismo método
- la lambda se crea en el constructor / class initialize porque más tarde el use-site
- ser llamado por varios subprocesos al mismo tiempo
- sufre el menor rendimiento de la primera invocación