¿Una expresión lambda crea un objeto en el montón cada vez que se ejecuta?
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 antigua" que aparece a continuación?
myStream.forEach(new Consumer<Item>() {
@Override
public void accept(Item item) {
// do something useful
}
});
¿Significa esto que Consumer
se crea un nuevo objeto anónimo en el montón cada vez que repito una colección? ¿Cuánto espacio de pila ocupa esto? ¿Qué implicaciones de rendimiento tiene? ¿Significa que debería utilizar el estilo antiguo para bucles al iterar sobre grandes estructuras de datos multinivel?
Es equivalente pero no idéntico. En pocas palabras, si una expresión lambda no captura valores, será un singleton que se reutilizará en cada invocación.
El comportamiento no está exactamente especificado. La JVM tiene 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 singletons para todas las expresiones que no capturan valores.
Puede leer esta respuesta para obtener 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 clases diferentes (si los cuerpos son idénticos, por ejemplo).
No es necesario que cada objeto producido por la evaluación pertenezca a la misma clase (las variables locales capturadas pueden estar integradas, por ejemplo).
Si hay una "instancia existente" disponible, no es necesario que se haya creado en una evaluación lambda anterior (por ejemplo, podría haberse asignado durante la inicialización de la clase adjunta).
El momento en que se crea una instancia que representa la lambda depende del contenido exacto del cuerpo de la lambda. Es decir, el factor clave es lo que la lambda captura 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 al bucle for-each. En su lugar, se generará un método sintético en el momento de la compilación y el sitio de uso lambda simplemente recibirá un objeto singleton que delega a ese método.
Tenga en cuenta además que este aspecto depende de la implementación y puede esperar mejoras y avances futuros en HotSpot hacia una mayor eficiencia. Hay planes generales para, por ejemplo, crear un objeto liviano sin una clase correspondiente completa, que tenga suficiente información para reenviar a un solo método.
Aquí hay un artículo bueno y accesible en profundidad sobre el tema:
http://www.infoq.com/articles/Java-8-Lambdas-A-Peek-Under-the-Hood
Estás pasando una nueva instancia al forEach
método. Cada vez que haces eso, creas un nuevo objeto, pero no uno para cada iteración del bucle. La iteración se realiza dentro del forEach
método utilizando la misma instancia de objeto de 'devolución de llamada' hasta que finaliza con el bucle.
Entonces la memoria utilizada por el bucle no depende del tamaño de la colección.
¿No es esto equivalente al fragmento de "sintaxis antigua"?
Sí. Tiene ligeras diferencias a un nivel muy bajo pero no creo que debas preocuparte por ellas. Las expresiones Lamba utilizan la característica invokedynamic en lugar de clases anónimas.