Java8 Lambdas vs classes anonymes


111

Puisque Java8 a été récemment publié et que ses toutes nouvelles expressions lambda ont l'air d'être vraiment cool, je me demandais si cela signifiait la disparition des classes Anonymous auxquelles nous étions si habitués.

J'ai fait des recherches un peu à ce sujet et j'ai trouvé quelques exemples intéressants sur la façon dont les expressions Lambda remplaceront systématiquement ces classes, comme la méthode de tri de Collection, qui permettait d'obtenir une instance anonyme de Comparator pour effectuer le tri:

Collections.sort(personList, new Comparator<Person>(){
  public int compare(Person p1, Person p2){
    return p1.firstName.compareTo(p2.firstName);
  }
});

Maintenant, cela peut être fait en utilisant Lambdas:

Collections.sort(personList, (Person p1, Person p2) -> p1.firstName.compareTo(p2.firstName));

Et semble étonnamment concis. Ma question est donc la suivante: y a-t-il une raison de continuer à utiliser ces classes en Java8 au lieu de Lambdas?

ÉDITER

Même question mais dans le sens inverse, quels sont les avantages d'utiliser Lambdas au lieu des classes Anonymes, puisque Lambdas ne peut être utilisé qu'avec des interfaces à méthode unique, cette nouvelle fonctionnalité n'est-elle qu'un raccourci utilisé uniquement dans quelques cas ou est-ce vraiment utile?


5
Bien sûr, pour toutes ces classes anonymes qui fournissent des méthodes avec des effets secondaires.
tobias_k

11
Juste pour votre information, vous pouvez également construire le comparateur comme:, Comparator.comparing(Person::getFirstName)si getFirstName()serait une méthode retournant firstName.
skiwi

1
Ou des classes anonymes avec plusieurs méthodes, ou ...
Mark Rotteveel

1
Je suis tenté de voter pour close car trop large, en particulier en raison des questions supplémentaires après EDIT .
Mark Rotteveel

1
Un bel article détaillé sur ce sujet: infoq.com/articles/Java-8-Lambdas-A-Peek-Under-the-Hood
Ram Patra

Réponses:


108

Une classe interne anonyme (AIC) peut être utilisée pour créer une sous-classe d'une classe abstraite ou d'une classe concrète. Une AIC peut également fournir une implémentation concrète d'une interface, y compris l'ajout d'état (champs). Une instance d'une AIC peut être référencée en utilisant thisdans ses corps de méthode, ainsi d'autres méthodes peuvent être appelées sur elle, son état peut être muté au fil du temps, etc. Aucun de ces éléments ne s'applique aux lambdas.

Je suppose que la majorité des utilisations des AIC étaient de fournir des implémentations sans état de fonctions uniques et peuvent donc être remplacées par des expressions lambda, mais il existe d'autres utilisations des AIC pour lesquelles les lambdas ne peuvent pas être utilisées. Les AIC sont là pour rester.

METTRE À JOUR

Une autre différence entre les AIC et les expressions lambda est que les AIC introduisent une nouvelle portée. Autrement dit, les noms sont résolus à partir des superclasses et des interfaces de l'AIC et peuvent masquer les noms qui se produisent dans l'environnement lexical englobant. Pour les lambdas, tous les noms sont résolus lexicalement.


1
Les lambdas peuvent avoir un état. À cet égard, je ne vois aucune différence entre Lambdas et AIC.
nosid

1
Les AIC @nosid, comme les instances de n'importe quelle classe, peuvent contenir un état dans des champs, et cet état est accessible à (et potentiellement modifiable par) n'importe quelle méthode de la classe. Cet état existe jusqu'à ce que l'objet soit GC, c'est-à-dire qu'il a une étendue indéfinie, donc il peut persister à travers les appels de méthode. Le seul état avec une étendue indéfinie de lambdas est capturé au moment où le lambda est rencontré; cet état est immuable. Les variables locales dans un lambda sont modifiables, mais elles n'existent que lorsqu'un appel lambda est en cours.
Stuart marque

1
@nosid Ah, le hack de tableau à un seul élément. N'essayez simplement pas d'utiliser votre compteur à partir de plusieurs threads. Si vous allez allouer quelque chose sur le tas et le capturer dans un lambda, vous pouvez aussi bien utiliser un AIC et ajouter un champ que vous pouvez muter directement. Utiliser un lambda de cette façon peut fonctionner, mais pourquoi s'embêter quand on peut utiliser un objet réel?
Stuart marque

2
AIC créera un fichier comme celui-ci, A$1.classmais pas Lambda. Puis-je ajouter ceci dans Différence?
Asif Mushtaq

2
@ Inconnu C'est principalement un problème de mise en œuvre; cela n'affecte pas la façon dont on programme avec les AIC par rapport aux lambdas, ce qui concerne principalement cette question. Notez qu'une expression lambda génère une classe avec un nom comme LambdaClass$$Lambda$1/1078694789. Cependant, cette classe est générée à la volée par la métafactory lambda, pas par javac, il n'y a donc pas de .classfichier correspondant . Encore une fois, cependant, il s'agit d'un problème de mise en œuvre.
Stuart marque le

60

Lambdas, bien qu'une fonctionnalité intéressante, ne fonctionnera qu'avec les types SAM. Autrement dit, s'interface avec une seule méthode abstraite. Il échouerait dès que votre interface contiendrait plus d'une méthode abstraite. C'est là que les classes anonymes seront utiles.

Donc, non, nous ne pouvons pas simplement ignorer les classes anonymes. Et juste pour info, votre sort()méthode peut être plus simplifiée, en ignorant la déclaration de type pour p1et p2:

Collections.sort(personList, (p1, p2) -> p1.firstName.compareTo(p2.firstName));

Vous pouvez également utiliser la référence de méthode ici. Soit vous ajoutez une compareByFirstName()méthode dans la Personclasse, et utilisez:

Collections.sort(personList, Person::compareByFirstName);

ou, ajoutez un getter pour firstName, obtenez directement la méthode Comparatorfrom Comparator.comparing():

Collections.sort(personList, Comparator.comparing(Person::getFirstName));

4
Je savais, mais je préfère la longue en termes de lisibilité, car sinon il pourrait être déroutant de savoir d'où viennent ces variables.
Amin Abu-Taleb

@ AminAbu-Taleb Pourquoi serait-ce déroutant. C'est une syntaxe Lambda valide. Les types sont de toute façon déduits. Quoi qu'il en soit, c'est un choix personnel. Vous pouvez donner des types explicites. Pas d'issues.
Rohit Jain

1
Il existe une autre différence subtile entre les lambdas et les classes anonymes: les classes anonymes peuvent être directement annotées avec les nouvelles annotations de type Java 8 , comme par exemple new @MyTypeAnnotation SomeInterface(){};. Cela n'est pas possible pour les expressions lambda. Pour plus de détails, consultez ma question ici: Annoter l'interface fonctionnelle d'une expression Lambda .
Balder

36

Performances Lambda avec les classes anonymes

Lorsque l'application est lancée, chaque fichier de classe doit être chargé et vérifié.

Les classes anonymes sont traitées par le compilateur comme un nouveau sous-type pour la classe ou l'interface donnée, de sorte qu'un nouveau fichier de classe sera généré pour chacune.

Les lambdas sont différents lors de la génération de bytecode, ils sont plus efficaces, ils utilisent l'instruction invokedynamic fournie avec JDK7.

Pour Lambdas, cette instruction est utilisée pour retarder la traduction de l'expression lambda dans le bytecode jusqu'à l'exécution. (l'instruction ne sera invoquée que pour la première fois)

En conséquence, l'expression Lambda deviendra une méthode statique (créée au moment de l'exécution). (Il y a une petite différence avec les statels et les cas avec état, ils sont résolus via des arguments de méthode générés)


Chaque lambda a également besoin d'une nouvelle classe, mais elle est générée lors de l'exécution, donc dans ce sens, les lambda ne sont pas plus efficaces que les classes anonymes. Les lambdas sont créés via invokedynamicce qui est généralement plus lent que celui invokespecialutilisé pour créer de nouvelles instances de classes anonymes. Donc, dans ce sens, les lambdas sont également plus lentes (cependant, la JVM peut optimiser les invokedynamicappels la plupart du temps).
ZhekaKozlov

3
@AndreiTomashpolskiy 1. Soyez poli. 2. Lisez ce commentaire d'un ingénieur du compilateur: habrahabr.ru/post/313350/comments/#comment_9885460
ZhekaKozlov

@ZhekaKozlov, vous n'avez pas besoin d'être un ingénieur de compilation pour lire le code source JRE et utiliser javap / debugger. Ce qui vous manque, c'est que la génération d'une classe wrapper pour une méthode lambda se fait entièrement en mémoire et ne coûte presque rien, tandis que l'instanciation d'un AIC implique la résolution et le chargement de la ressource de classe correspondante (ce qui signifie un appel système d'E / S). Par conséquent, invokedynamicavec la génération de classes ad hoc, la génération est extrêmement rapide par rapport aux classes anonymes compilées.
Andrei Tomashpolskiy

@AndreiTomashpolskiy I / O n'est pas nécessairement lent
ZhekaKozlov

14

Il existe les différences suivantes:

1) Syntaxe

Les expressions Lambda semblent soignées par rapport à la classe interne anonyme (AIC)

public static void main(String[] args) {
    Runnable r = new Runnable() {
        @Override
        public void run() {
            System.out.println("in run");
        }
    };

    Thread t = new Thread(r);
    t.start(); 
}

//syntax of lambda expression 
public static void main(String[] args) {
    Runnable r = ()->{System.out.println("in run");};
    Thread t = new Thread(r);
    t.start();
}

2) Portée

Une classe interne anonyme est une classe, ce qui signifie qu'elle a une portée pour la variable définie à l'intérieur de la classe interne.

Alors que l' expression lambda n'est pas une portée en soi, mais fait partie de la portée englobante.

Une règle similaire s'applique pour super et ce mot clé lors de l'utilisation à l'intérieur d'une classe interne anonyme et d'une expression lambda. Dans le cas d'une classe interne anonyme, ce mot clé fait référence à la portée locale et le mot clé super fait référence à la super classe de la classe anonyme. Alors que dans le cas d'une expression lambda, ce mot-clé fait référence à l'objet du type englobant et super fera référence à la super classe de la classe englobante.

//AIC
    public static void main(String[] args) {
        final int cnt = 0; 
        Runnable r = new Runnable() {
            @Override
            public void run() {
                int cnt = 5;    
                System.out.println("in run" + cnt);
            }
        };

        Thread t = new Thread(r);
        t.start();
    }

//Lambda
    public static void main(String[] args) {
        final int cnt = 0; 
        Runnable r = ()->{
            int cnt = 5; //compilation error
            System.out.println("in run"+cnt);};
        Thread t = new Thread(r);
        t.start();
    }

3) Performance

Au moment de l'exécution, les classes internes anonymes nécessitent le chargement de classe, l'allocation de mémoire, l'initialisation d'objet et l'appel d'une méthode non statique, tandis que l'expression lambda est une activité de compilation pure et n'entraîne pas de coût supplémentaire pendant l'exécution. Les performances de l'expression lambda sont donc meilleures que celles des classes internes anonymes. **

** Je me rends compte que ce point n'est pas entièrement vrai. Veuillez vous référer à la question suivante pour plus de détails. Lambda vs performances de classe interne anonyme: réduire la charge sur le ClassLoader?


3

Lambda en java 8 a été introduit pour la programmation fonctionnelle. Où vous pouvez éviter le code passe-partout. Je suis tombé sur cet article intéressant sur les lambda.

http://radar.oreilly.com/2014/04/whats-new-in-java-8-lambdas.html

Il est conseillé d'utiliser les fonctions lambda pour les logiques simples. Si l'implémentation d'une logique complexe à l'aide de lambdas sera une surcharge dans le débogage du code en cas de problème.


0

Les classes anonymes sont là pour rester car lambda est bon pour les fonctions avec des méthodes abstraites uniques mais pour tous les autres cas, les classes internes anonymes sont votre sauveur.


-1
  • La syntaxe lambda ne nécessite pas d'écrire le code évident que java peut déduire.
  • En utilisant invoke dynamic , lambda ne sont pas reconvertis en classes anonymes pendant la compilation (Java n'a pas à passer par la création d'objets, se soucie juste de la signature de la méthode, peut se lier à la méthode sans créer d'objet
  • lambda met davantage l'accent sur ce que nous voulons faire plutôt que sur ce que nous devons faire avant de pouvoir le faire
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.