L'utilisation de la syntaxe de référence de méthode à la place de la syntaxe lambda dans Java 8 présente-t-elle un avantage en termes de performances?

56

Les références aux méthodes ignorent-elles les frais généraux du wrapper lambda? Pourraient-ils dans le futur?

Selon le tutoriel Java sur les références de méthode :

Parfois ... une expression lambda ne fait qu'appeler une méthode existante. Dans ces cas, il est souvent plus clair de faire référence à la méthode existante par son nom. Les références de méthode vous permettent de le faire; Ce sont des expressions lambda compactes et faciles à lire pour les méthodes qui ont déjà un nom.

Je préfère la syntaxe lambda à la syntaxe de référence de la méthode pour plusieurs raisons:

Lambdas sont plus claires

Malgré les affirmations d'Oracle, je trouve la syntaxe lambda plus facile à lire que la référence à la méthode de référence de l'objet car la syntaxe de référence de la méthode est ambiguë:

Bar::foo

Appelez-vous une méthode statique à un argument sur la classe de x et transmettez-vous x?

x -> Bar.foo(x)

Ou appelez-vous une méthode d'instance à zéro argument sur x?

x -> x.foo()

La syntaxe de référence de la méthode peut remplacer l'un ou l'autre. Il cache ce que votre code est en train de faire.

Lambdas sont plus sûrs

Si vous faites référence à Bar :: foo en tant que méthode de classe et que Bar ajoute ultérieurement une méthode d'instance du même nom (ou inversement), votre code ne sera plus compilé.

Vous pouvez utiliser des lambdas de manière constante

Vous pouvez envelopper n'importe quelle fonction dans un lambda - afin que vous puissiez utiliser la même syntaxe partout. La syntaxe de référence de méthode ne fonctionne pas avec les méthodes qui prennent ou renvoient des tableaux primitifs, lèvent des exceptions vérifiées ou ont le même nom de méthode utilisé comme instance et une méthode statique (car la syntaxe de référence de méthode est ambiguë quant à la méthode à appeler) . Ils ne fonctionnent pas lorsque vous avez surchargé des méthodes avec le même nombre d'arguments, mais vous ne devriez pas le faire de toute façon (voir le point 41 de Josh Bloch), nous ne pouvons donc pas en tenir autant aux références à des méthodes.

Conclusion

Si cela ne nuit pas aux performances, je suis tenté de désactiver l'avertissement dans mon IDE et d'utiliser la syntaxe lambda de manière cohérente sans saupoudrer la référence de méthode occasionnelle dans mon code.

PS

Ni ici ni là-bas, mais dans mes rêves, les références de méthode d'objet ressemblent davantage à ceci et appliquent invoke-dynamic à la méthode directement sur l'objet sans wrapper lambda:

_.foo()
GlenPeterson
la source
2
Cela ne répond pas à votre question, mais il existe d'autres raisons d'utiliser les fermetures. À mon avis, beaucoup d’entre eux dépassent de loin les frais généraux inhérents à un appel de fonction supplémentaire.
9
Je pense que vous vous inquiétez de la performance prématurément. À mon avis, la performance devrait être une considération secondaire pour l'utilisation d'une référence de méthode; il s’agit d’exprimer votre intention. Si vous avez besoin de passer une fonction quelque part, pourquoi ne pas simplement passer la fonction à la place d’une autre fonction qui la délègue? Cela me semble étrange. comme si vous n'achetiez pas vraiment que les fonctions sont des choses de première classe, ils ont besoin d'un chaperon anonyme pour les déplacer. Mais pour répondre plus directement à votre question, je ne serais pas surpris de savoir si une référence de méthode peut être implémentée en tant qu’appel statique.
Doval
7
.map(_.acceptValue()): Si je ne me trompe pas, cela ressemble beaucoup à la syntaxe Scala. Peut-être que vous essayez simplement d'utiliser le mauvais langage.
Giorgio
2
"La syntaxe de référence des méthodes ne fonctionnera pas avec les méthodes qui renvoient void" - Comment? Je passe System.out::printlnà forEach()tout le temps ...?
Lukas Eder
3
"La syntaxe de référence des méthodes ne fonctionnera pas avec les méthodes qui prennent ou renvoient des tableaux primitifs, jettent les exceptions vérifiées ...." Ce n'est pas correct. Vous pouvez utiliser des références de méthode ainsi que des expressions lambda pour de tels cas, à condition que l'interface fonctionnelle en question le permette.
Stuart marque le

Réponses:

12

Dans de nombreux scénarios, je pense que lambda et method-reference sont équivalents. Mais le lambda encapsulera la cible d'invocation par le type d'interface déclarant.

Par exemple

public class InvokeTest {

    private static void invoke(final Runnable r) {
        r.run();
    }

    private static void target() {
        new Exception().printStackTrace();
    }

    @Test
    public void lambda() throws Exception {
        invoke(() -> target());
    }

    @Test
    public void methodReference() throws Exception {
        invoke(InvokeTest::target);
    }
}

Vous verrez la console sortir le stacktrace.

Dans lambda(), l'appel de méthode target()est lambda$lambda$0(InvokeTest.java:20), qui a des informations de ligne traçables. Évidemment, c’est le lambda que vous écrivez, le compilateur génère une méthode anonyme pour vous. Et puis, l'appelant de la méthode lambda ressemble à quelque chose comme InvokeTest$$Lambda$2/1617791695.run(Unknown Source): c'est l' invokedynamicappel de la machine virtuelle Java, cela signifie que l'appel est lié à la méthode générée .

En methodReference()appelant target()directement la méthode InvokeTest$$Lambda$1/758529971.run(Unknown Source), cela signifie que l'appel est directement lié à la InvokeTest::targetméthode .

Conclusion

Surtout, comparez-vous à la méthode-référence, utiliser l'expression lambda ne provoquera qu'un seul appel supplémentaire à la méthode génératrice à partir de lambda.

JasonMing
la source
1
La réponse ci-dessous à propos de la méta-usine est un peu meilleure. J'ai ajouté quelques commentaires utiles qui s'y rapportent (notamment la façon dont des implémentations telles que Oracle / OpenJDK utilisent ASM pour générer les objets de classe wrapper nécessaires à la création d'instances de descripteurs de méthode / méthode.)
Ajax le
29

Tout tourne autour de la méta - usine

Premièrement, la plupart des références de méthode n’ont pas besoin d’être étudiées par la métafactory lambda, elles sont simplement utilisées comme méthode de référence. Sous la section "Sucre du corps Lambda" de l'article sur la traduction des expressions lambda ("TLE"):

Toutes choses étant égales par ailleurs, les méthodes privées sont préférables aux méthodes non privées, les méthodes statiques étant préférables aux méthodes d'instance, il est préférable que les corps lambda soient désuètes dans la classe la plus interne dans laquelle l'expression lambda apparaît, les signatures doivent correspondre à la signature de corps du lambda, extra les arguments doivent être précédés du début de la liste des arguments pour les valeurs capturées, et ne feront aucunement référence aux méthodes. Cependant, il existe des cas exceptionnels où nous pouvons être amenés à nous écarter de cette stratégie de base.

Ceci est davantage souligné plus bas dans "The Lambda Metafactory" de TLE:

metaFactory(MethodHandles.Lookup caller, // provided by VM
            String invokedName,          // provided by VM
            MethodType invokedType,      // provided by VM
            MethodHandle descriptor,     // lambda descriptor
            MethodHandle impl)           // lambda body

L' implargument identifie la méthode lambda, soit un corps lambda desugared, soit la méthode nommée dans une référence de méthode.

Les références à une Integer::summéthode statique ( ) ou à une instance non liée ( Integer::intValue) sont les plus simples ou les plus pratiques, dans la mesure où elles peuvent être gérées de manière optimale par une variante métafabrique à «chemin rapide» sans désugaration . Cet avantage est utilement souligné dans les "variantes Metafactory" de TLE:

En éliminant les arguments là où ils ne sont pas nécessaires, les fichiers de classe deviennent plus petits. De plus, l'option de voie rapide réduit la barre pour que la machine virtuelle intrinsèque l'opération de conversion lambda, ce qui lui permet d'être traitée comme une opération de "boxing" et de faciliter les optimisations de la boîte de réception.

Naturellement, une méthode de capture d'instance reference ( obj::myMethod) doit fournir l'instance liée en tant qu'argument au descripteur de méthode pour l'invocation, ce qui peut signifier la nécessité de désinsectiser à l'aide de méthodes 'bridge'.

Conclusion

Je ne suis pas tout à fait sûr de la "enveloppe" lambda à laquelle vous faites allusion, mais même si le résultat final de l'utilisation de vos lambdas définis par l'utilisateur ou de vos références de méthode est le même, la méthode atteinte semble être très différente, et peut être différent à l'avenir si ce n'est pas le cas maintenant. Par conséquent, je suppose qu'il est plus probable qu'improbable que la méta-usine puisse traiter les références de méthode de manière plus optimale.

hjk
la source
1
C'est la réponse la plus pertinente, car elle touche en fait aux mécanismes internes nécessaires à la création d'un lambda. En tant que personne qui a réellement débogué le processus de création de lambda (afin de comprendre comment générer des identifiants stables pour une référence lambda / méthode donnée afin de les supprimer des cartes), j’ai trouvé assez surprenant à quel point le travail effectué pour créer une exemple d'un lambda. Oracle / OpenJDK utilise ASM pour générer de manière dynamique des classes qui, le cas échéant, se ferment sur toute variable référencée dans votre lambda (ou le qualificatif d'instance d'une référence de méthode) ...
Ajax le
1
... En plus de la fermeture des variables, lambdas en particulier crée également une méthode statique injectée dans la classe déclarante afin que le lambda créé ait quelque chose à appeler à mapper dans la ou les fonctions à appeler. Une référence de méthode avec un qualificateur d'instance prendra cette instance en tant que paramètre de cette méthode; Je n'ai pas testé si une référence this :: méthode dans une classe privée ignore cette fermeture, mais j'ai également vérifié que les méthodes statiques ignoraient en fait la méthode intermédiaire (et créaient simplement un objet à conformer à la cible d'invocation à la site d'appel).
Ajax
1
Vraisemblablement, une méthode privée n’aura pas non plus besoin d’une répartition virtuelle, alors que tout ce qui est plus public devra se fermer sur l’instance (via une méthode statique générée) afin de pouvoir invoquer la virtualité sur l’instance réelle pour s’assurer qu’elle remarque les remplacements. TL; DR: Les références de méthodes se ferment avec moins d'arguments, elles fuient moins et nécessitent une génération de code moins dynamique.
Ajax
3
Dernier commentaire spammé ... La génération de classe dynamique est assez lourde. Cela vaut probablement mieux que de charger une classe anonyme dans le chargeur de classe (car les parties les plus lourdes ne sont exécutées qu’une seule fois), mais vous seriez vraiment surpris du travail nécessaire à la création d’une instance lambda. Il serait intéressant de voir de réels repères lambdas versus références de méthodes versus classes anonymes. Notez que deux lambdas ou références de méthodes qui référencent les mêmes choses, mais à des endroits différents, créent des classes différentes au moment de l'exécution (même au sein de la même méthode, juste après l'autre).
Ajax
@Ajax Qu'en est-il de celui-ci? blog.soat.fr/2015/12/benchmark-java-lambda-vs-classe-anonyme .
Walfrat
0

Il y a une conséquence assez grave lorsqu’on utilise des expressions lambda qui peut affecter les performances.

Lorsque vous déclarez une expression lambda, vous créez une fermeture sur la portée locale.

Qu'est-ce que cela signifie et en quoi cela affecte-t-il les performances?

Eh bien, je suis content que vous avez demandé. Cela signifie que chacune de ces petites expressions lambda est une petite classe interne anonyme, ce qui signifie qu'elle comporte une référence à toutes les variables qui appartiennent au même champ d'application de l'expression lambda.

Cela signifie également la thisréférence de l'instance d'objet et de tous ses champs. En fonction du moment de l'invocation réelle du lambda, cela peut constituer une fuite de ressource assez importante car le garbage collector est incapable de lâcher ces références tant que l'objet contenant un lambda est toujours en vie ...

Roland Tepp
la source
Il en va de même pour les références de méthodes non statiques.
Ajax
12
Les lambdas de Java ne sont pas strictement des fermetures. Ce ne sont pas non plus des classes intérieures anonymes. Ils ne portent PAS une référence à toutes les variables dans la portée où elles sont déclarées. Ils portent UNIQUEMENT une référence aux variables qu’ils référencent réellement. Ceci s'applique également à l' thisobjet qui pourrait être référencé. Un lambda ne perd donc pas de ressources simplement en ayant la possibilité de les utiliser; il ne conserve que les objets dont il a besoin. D'autre part, une classe interne anonyme peut perdre des ressources, mais ne le fait pas toujours. Voir ce code pour un exemple: a.blmq.us/2mmrL6v
squid314
Cette réponse est tout simplement fausse
Stefan Reich
Merci @StefanReich, c'était très utile!
Roland Tepp