Une expression lambda crée-t-elle un object sur le tas chaque fois qu’il est exécuté?

Lorsque je parcourt une collection en utilisant le nouveau sucre syntaxique de Java 8, tel que

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

N’est-ce pas l’équivalent de l’extrait de «l’ancienne syntaxe» ci-dessous?

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

Est-ce que cela signifie qu’un nouvel object Consumer anonyme est créé sur le tas chaque fois que je parcourt une collection? Combien de tas cela prend-il? Quelles sont ses implications sur les performances? Cela signifie-t-il que je devrais plutôt utiliser l’ancien style pour les boucles lors d’une itération sur de grandes structures de données à plusieurs niveaux?

C’est équivalent mais pas identique. En termes simples, si une expression lambda ne capture pas les valeurs, ce sera un singleton qui sera réutilisé à chaque appel.

Le comportement n’est pas exactement spécifié. La JVM dispose d’une grande liberté pour la mettre en œuvre. Actuellement, la machine virtuelle Java d’Oracle crée (au moins) une instance par expression lambda (c’est-à-dire qu’elle ne partage pas d’instance entre différentes expressions identiques) mais crée un singleton pour toutes les expressions qui ne capturent pas de valeurs.

Vous pouvez lire cette réponse pour plus de détails. Là, non seulement j’ai donné une description plus détaillée, mais aussi un code de test pour observer le comportement actuel.


Ceci est couvert par la spécification du langage Java®, chapitre « 15.27.4. Évaluation du temps d’exécution des expressions lambda »

Résumé:

Ces règles sont conçues pour offrir de la flexibilité aux implémentations du langage de programmation Java, à savoir:

  • Un nouvel object n’a pas besoin d’être affecté à chaque évaluation.

  • Les objects produits par différentes expressions lambda ne doivent pas nécessairement appartenir à des classes différentes (si les corps sont identiques, par exemple).

  • Chaque object produit par une évaluation ne doit pas nécessairement appartenir à la même classe (les variables locales capturées peuvent être incluses, par exemple).

  • Si une “instance existante” est disponible, elle n’a pas besoin d’être créée lors d’une évaluation lambda précédente (par exemple, elle peut avoir été allouée lors de l’initialisation de la classe englobante).

Lorsqu’une instance représentant le lambda est créée, la sensibilité dépend du contenu exact du corps de votre lambda. À savoir, le facteur clé est ce que le lambda capture de l’environnement lexical. S’il ne capture aucun état variable de la création à la création, une instance ne sera pas créée chaque fois que la boucle for-each est entrée. Au lieu de cela, une méthode synthétique sera générée au moment de la compilation et le site d’utilisation lambda recevra simplement un object singleton qui délègue à cette méthode.

Notez en outre que cet aspect dépend de la mise en œuvre et que vous pouvez vous attendre à de nouvelles améliorations et avancées sur HotSpot pour une plus grande efficacité. Il existe des plans généraux pour par exemple créer un object léger sans une classe complète correspondante, qui a juste assez d’informations pour être transférée vers une méthode unique.

Voici un bon article accessible en profondeur sur le sujet:

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

Vous transmettez une nouvelle instance à la méthode forEach . Chaque fois que vous le faites, vous créez un nouvel object, mais pas un pour chaque itération de boucle. L’itération est effectuée à l’intérieur de la méthode forEach utilisant la même instance d’object ‘callback’ jusqu’à ce qu’elle soit terminée avec la boucle.

La mémoire utilisée par la boucle ne dépend donc pas de la taille de la collection.

N’est-ce pas l’équivalent de l’extrait de «l’ancienne syntaxe»?

Oui. Il y a de légères différences à un niveau très bas mais je ne pense pas que vous devriez vous en soucier. Les expressions Lamba utilisent la fonctionnalité invokedynamic au lieu des classes anonymes.

 List list = new ArrayList() { @Override public void forEach(Consumer action) { // just print the lambda hash code System.out.println(action.hashCode()); } }; for (int i = 0; i < 2; i++) { list.forEach(o -> { // do something useful }); } 

Sur ma boîte, il apparaît comme le même object:

 142257191
 142257191

Lors de l’utilisation de la référence de méthode, un object différent est créé:

 for (int i = 0; i < 2; i++) { list.forEach(System.out::println); } 

Sortie:

 1044036744
 1826771953

MacOS High Sierra
version java "1.8.0_181"
Java (TM) SE Runtime Environment (version 1.8.0_181-b13)
VM serveur Java HotSpot (64 bits) (version 25.181-b13, mode mixte)