web-dev-qa-db-fra.com

Y a-t-il un avantage en termes de performances à utiliser la syntaxe de référence de méthode au lieu de la syntaxe lambda dans Java 8?

Les références de méthode ignorent-elles la surcharge du wrapper lambda? Pourraient-ils à l'avenir?

Selon le Tutoriel Java sur les références de méthodes :

Parfois ... une expression lambda ne fait qu'appeler une méthode existante. Dans ces cas, il est souvent plus clair de se référer à 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 méthode pour plusieurs raisons:

Les lambdas sont plus clairs

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

Bar::foo

Appelez-vous une méthode statique à un argument sur la classe de x et passez-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 méthode peut remplacer l'une ou l'autre. Il cache ce que fait réellement votre code.

Les lambdas sont plus sûrs

Si vous référencez Bar :: foo comme méthode de classe et que Bar ajoute plus tard une méthode d'instance du même nom (ou vice-versa), votre code ne sera plus compilé.

Vous pouvez utiliser des lambdas de manière cohérente

Vous pouvez encapsuler n'importe quelle fonction dans un lambda - vous pouvez donc utiliser la même syntaxe de manière cohérente partout. La syntaxe de référence de méthode ne fonctionnera pas sur les méthodes qui prennent ou retournent des tableaux primitifs, lèvent des exceptions vérifiées ou ont le même nom de méthode utilisé comme instance et méthode statique (car la syntaxe de référence de méthode est ambiguë quant à la méthode qui sera appelée) . 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 tenir cela contre les références de méthode.

Conclusion

S'il n'y a pas de pénalité de performance pour cela, 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.

P.S.

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

_.foo()
61
GlenPeterson

Dans de nombreux scénarios, je pense que lambda et référence de méthode sont équivalents. Mais le lambda encapsulera la cible d'invocation par le type d'interface déclarante.

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(), la méthode appelant 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 pour vous une méthode anonyme. Et puis, l'appelant de la méthode lambda est quelque chose comme InvokeTest$$Lambda$2/1617791695.run(Unknown Source), c'est-à-dire l'appel invokedynamic dans JVM, cela signifie que l'appel est lié à la méthode générée.

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

Conclusion

Par-dessus tout, comparez à la référence de méthode, l'utilisation de l'expression lambda ne fera que n plus d'appel de méthode à la méthode de génération à partir de lambda.

15
JasonMing

Tout tourne autour de la metafactory

Tout d'abord, la plupart des références de méthode n'ont pas besoin d'être supprimées par la métafabrique lambda, elles sont simplement utilisées comme méthode de référence. Sous la section "Lambda body sugaring" de l'article Translation of Lambda Expressions ("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 préférables aux méthodes d'instance, il est préférable que les corps lambda soient désagrégés dans la classe la plus intérieure dans laquelle l'expression lambda apparaît, les signatures doivent correspondre à la signature du corps du lambda, extra les arguments doivent être ajoutés au début de la liste des arguments pour les valeurs capturées et ne désugarent pas du tout les références de méthode. Cependant, il existe des cas d'exception où nous pourrions avoir à dévier de cette stratégie de base.

Ceci est davantage souligné plus loin 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'argument impl identifie la méthode lambda, soit un corps lambda désucré, soit la méthode nommée dans une référence de méthode.

Un statique (Integer::sum) ou méthode d'instance illimitée (Integer::intValue) les références sont les 'plus simples' ou les plus 'pratiques', en ce sens qu'elles peuvent être gérées de manière optimale par un 'fast-path' variante métafactory sans le desugaring . 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. Et l'option de chemin rapide abaisse la barre pour le VM pour intrinsèquement l'opération de conversion lambda, lui permettant d'être traitée comme une opération de "boxe" et facilitant les optimisations de déballage.

Naturellement, une référence de méthode de capture d'instance (obj::myMethod) doit fournir l'instance bornée en tant qu'argument au descripteur de méthode pour l'invocation, ce qui peut signifier la nécessité de désugarer en utilisant les méthodes 'bridge'.

Conclusion

Je ne sais pas exactement quel est le `` wrapper '' lambda auquel vous faites allusion, mais même si le résultat final de l'utilisation de vos lambdas ou références de méthode définis par l'utilisateur est le même, la manière qui est atteint semble être tout à fait différent, 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 les références de méthode puissent être traitées de manière plus optimale par la métafactory.

31
h.j.k.

Il y a une conséquence assez grave lors de l'utilisation d'expressions lambda qui peut affecter les performances.

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

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

Eh bien, je suis content que vous ayez demandé. Cela signifie que chacune de ces petites expressions lambda est une petite classe interne anonyme et cela signifie qu'elle porte avec elle une référence à toutes les variables qui sont dans la même portée de l'expression lambda.

Cela signifie également this référence de l'instance d'objet et de tous ses champs. Selon le moment de l'invocation réelle du lambda, cela peut représenter une fuite de ressources assez importante car le garbage collector est incapable de lâcher ces références tant que l'objet contenant un lambda est toujours vivant ...

0
Roland Tepp