Une expression lambda crée-t-elle un objet sur le tas à chaque exécution?

182

Lorsque j'itère une collection en utilisant le nouveau sucre syntaxique de Java 8, tel que

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

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

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

Cela signifie-t-il qu'un nouvel Consumerobjet anonyme est créé sur le tas chaque fois que j'itère sur une collection? Combien d'espace de tas cela prend-il? Quelles implications en termes de performances cela a-t-il? Cela signifie-t-il que je devrais plutôt utiliser l'ancien style pour les boucles lors de l'itération sur de grandes structures de données à plusieurs niveaux?

Bastian Voigt
la source
48
Réponse courte: non. Pour les lambdas sans état (ceux qui ne capturent rien de leur contexte lexical), une seule instance sera jamais créée (paresseusement) et mise en cache sur le site de capture. (C'est ainsi que fonctionne la mise en œuvre; la spécification a été soigneusement écrite pour autoriser, mais pas exiger, cette approche.)
Brian Goetz

Réponses:

158

C'est équivalent mais pas identique. Simplement dit, si une expression lambda ne capture pas de valeurs, ce sera un singleton qui est réutilisé à chaque appel.

Le comportement n'est pas exactement spécifié. La JVM a une grande liberté sur la façon de l'implémenter. Actuellement, la JVM d'Oracle crée (au moins) une instance par expression lambda (c'est-à-dire ne partage pas d'instance entre différentes expressions identiques) mais crée des singletons pour toutes les expressions qui ne capturent pas de valeurs.

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


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

Résumé:

Ces règles sont destinées à offrir de la flexibilité aux implémentations du langage de programmation Java, en ce que:

  • Il n'est pas nécessaire d'attribuer un nouvel objet à chaque évaluation.

  • Les objets produits par différentes expressions lambda n'ont pas besoin d'appartenir à des classes différentes (si les corps sont identiques, par exemple).

  • Chaque objet produit par l'évaluation n'a pas besoin d'appartenir à la même classe (les variables locales capturées peuvent être intégrées, par exemple).

  • Si une "instance existante" est disponible, il n'est pas nécessaire qu'elle ait été créée lors d'une évaluation lambda précédente (elle peut avoir été allouée lors de l'initialisation de la classe englobante, par exemple).

Holger
la source
24

La création d'une instance représentant le lambda 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, alors 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 objet 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 à des améliorations et des progrès futurs sur HotSpot vers une plus grande efficacité. Il existe des plans généraux pour, par exemple, créer un objet léger sans classe correspondante complète, qui a juste assez d'informations pour être transmis à une seule méthode.

Voici un bon article en profondeur accessible sur le sujet:

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

Marko Topolnik
la source
0

Vous transmettez une nouvelle instance à la forEachméthode. Chaque fois que vous faites cela, vous créez un nouvel objet mais pas un pour chaque itération de boucle. L'itération est effectuée à l'intérieur de la forEachméthode en utilisant la même instance d'objet 'callback' jusqu'à ce qu'elle soit faite avec la boucle.

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

N'est-ce pas équivalent à l'extrait de code «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.

aalku
la source
Avez-vous une documentation spécifiant cela? C'est une optimisation assez intéressante.
A. Rama
Merci, mais que faire si j'ai une collection de collections de collections, par exemple lors d'une recherche en profondeur sur une structure de données arborescente?
Bastian Voigt
2
@ A.Rama Désolé, je ne vois pas l'optimisation. C'est la même chose avec ou sans lambdas et avec ou sans boucle forEach.
aalku
1
Ce n'est pas vraiment la même chose, mais tout au plus un objet par niveau d'imbrication sera nécessaire à tout moment, ce qui est négligeable. Chaque nouvelle itération d'une boucle interne créera un nouvel objet, capturant très probablement l'élément actuel de la boucle externe. Cela crée une certaine pression GC, mais toujours rien à craindre.
Marko Topolnik
4
@aalku: " Chaque fois que vous faites cela, vous créez un nouvel objet ": Pas d'après cette réponse de Holger et ce commentaire de Brian Goetz .
Lii