¿Una expresión lambda crea un objeto en el montón cada vez que se ejecuta?

182

Cuando itero sobre una colección usando el nuevo azúcar sintáctico de Java 8, como

myStream.forEach(item -> {
  // do something useful
});

¿No es esto equivalente al fragmento de 'sintaxis anterior' a continuación?

myStream.forEach(new Consumer<Item>() {
  @Override
  public void accept(Item item) {
    // do something useful
  }
});

¿Significa esto Consumerque se crea un nuevo objeto anónimo en el montón cada vez que itero sobre una colección? ¿Cuánto espacio de almacenamiento ocupa esto? ¿Qué implicaciones de rendimiento tiene? ¿Significa que debería usar el estilo antiguo para los bucles al iterar sobre grandes estructuras de datos de varios niveles?

Bastian Voigt
fuente
48
Respuesta corta: no. Para las lambdas sin estado (aquellas que no capturan nada de su contexto léxico), solo se creará una instancia (perezosamente) y se almacenará en caché en el sitio de captura. (Así es como funciona la implementación; la especificación fue escrita cuidadosamente para permitir, pero no requerir, este enfoque).
Brian Goetz

Respuestas:

158

Es equivalente pero no idéntico. Simplemente dicho, si una expresión lambda no captura valores, será un singleton que se reutilizará en cada invocación.

El comportamiento no se especifica exactamente. La JVM tiene una gran libertad sobre cómo implementarla. Actualmente, la JVM de Oracle crea (al menos) una instancia por expresión lambda (es decir, no comparte instancia entre diferentes expresiones idénticas) pero crea tonos únicos para todas las expresiones que no capturan valores.

Puede leer esta respuesta para más detalles. Allí, no solo di una descripción más detallada sino que también probé el código para observar el comportamiento actual.


Esto está cubierto por la especificación del lenguaje Java®, capítulo “ 15.27.4. Evaluación en tiempo de ejecución de expresiones lambda

Resumido:

Estas reglas están destinadas a ofrecer flexibilidad a las implementaciones del lenguaje de programación Java, en el sentido de que:

  • No es necesario asignar un nuevo objeto en cada evaluación.

  • Los objetos producidos por diferentes expresiones lambda no necesitan pertenecer a diferentes clases (si los cuerpos son idénticos, por ejemplo).

  • No es necesario que todos los objetos producidos por evaluación pertenezcan a la misma clase (las variables locales capturadas pueden estar en línea, por ejemplo).

  • Si hay una "instancia existente" disponible, no es necesario que se haya creado en una evaluación lambda previa (por ejemplo, podría haberse asignado durante la inicialización de la clase adjunta).

Holger
fuente
24

Cuando se crea una instancia que representa la lambda de manera sensible, depende del contenido exacto del cuerpo de la lambda. A saber, el factor clave es lo que captura la lambda del entorno léxico. Si no captura ningún estado que sea variable de una creación a otra, entonces no se creará una instancia cada vez que se ingrese el ciclo for-each. En cambio, se generará un método sintético en el momento de la compilación y el sitio de uso lambda solo recibirá un objeto singleton que delega a ese método.

Además, tenga en cuenta que este aspecto depende de la implementación y puede esperar futuros refinamientos y avances en HotSpot hacia una mayor eficiencia. Hay planes generales para, por ejemplo, hacer un objeto liviano sin una clase correspondiente completa, que tenga suficiente información para reenviar a un solo método.

Aquí hay un buen artículo accesible en profundidad sobre el tema:

http://www.infoq.com/articles/Java-8-Lambdas-A-Peek-Under-the-Hood

Marko Topolnik
fuente
0

Está pasando una nueva instancia al forEachmétodo. Cada vez que haces eso, creas un nuevo objeto pero no uno para cada iteración de bucle. La iteración se realiza dentro del forEachmétodo utilizando la misma instancia de objeto 'devolución de llamada' hasta que se realiza con el bucle.

Por lo tanto, la memoria utilizada por el bucle no depende del tamaño de la colección.

¿No es esto equivalente al fragmento de "sintaxis antigua"?

Si. Tiene ligeras diferencias a un nivel muy bajo, pero no creo que debas preocuparte por ellas. Las expresiones de Lamba utilizan la función invocada dinámica en lugar de clases anónimas.

aalku
fuente
¿Tiene alguna documentación que especifique esto? Es una optimización bastante interesante.
A. Rama
Gracias, pero ¿qué pasa si tengo una colección de colecciones de colecciones, por ejemplo, cuando hago una búsqueda profunda en una estructura de datos de árbol?
Bastian Voigt
2
@ A.Rama Lo siento, no veo la optimización. Es lo mismo con o sin lambdas y con o sin para cada bucle.
aalku
1
No es realmente lo mismo, pero aún así, a lo sumo, se necesitará un objeto por nivel de anidación en cualquier momento, lo que es insignificante. Cada nueva iteración de un bucle interno creará un nuevo objeto, probablemente capturando el elemento actual del bucle externo. Esto crea cierta presión de GC, pero aún no hay nada de qué preocuparse.
Marko Topolnik
44
@aalku: " Cada vez que haces eso creas un nuevo objeto ": No según esta respuesta de Holger y este comentario de Brian Goetz .
Lii