Java8 Lambdas vs clases anónimas

111

Dado que Java8 se lanzó recientemente y sus nuevas expresiones lambda parecen ser realmente geniales, me preguntaba si esto significaría la desaparición de las clases Anónimas a las que estábamos tan acostumbrados.

He estado investigando un poco sobre esto y encontré algunos ejemplos interesantes sobre cómo las expresiones Lambda reemplazarán sistemáticamente esas clases, como el método de ordenación de Collection, que solía obtener una instancia anónima de Comparator para realizar la ordenación:

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

Ahora se puede hacer usando Lambdas:

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

Y parece sorprendentemente conciso. Entonces, mi pregunta es, ¿hay alguna razón para seguir usando esas clases en Java8 en lugar de Lambdas?

EDITAR

La misma pregunta pero en la dirección opuesta, ¿cuáles son los beneficios de usar Lambdas en lugar de clases anónimas, ya que Lambdas solo se puede usar con interfaces de un solo método, esta nueva característica es solo un acceso directo solo se usa en pocos casos o es realmente útil?

Amin Abu-Taleb
fuente
5
Seguro, para todas esas clases anónimas que proporcionan métodos con efectos secundarios.
tobias_k
11
Solo para su información, también puede construir el comparador como:, Comparator.comparing(Person::getFirstName)si getFirstName()sería un método que regresa firstName.
Skiwi
1
O clases anónimas con múltiples métodos, o ...
Mark Rotteveel
1
Me siento tentado a votar por cerrar por ser demasiado amplio, especialmente debido a las preguntas adicionales después de EDITAR .
Mark Rotteveel
1
Un buen artículo en profundidad sobre este tema: infoq.com/articles/Java-8-Lambdas-A-Peek-Under-the-Hood
Ram Patra

Respuestas:

108

Se puede usar una clase interna anónima (AIC) para crear una subclase de una clase abstracta o una clase concreta. Un AIC también puede proporcionar una implementación concreta de una interfaz, incluida la adición de estado (campos). Se puede hacer referencia a una instancia de un AIC thisen los cuerpos de sus métodos, por lo que se pueden llamar a otros métodos, su estado se puede mutar con el tiempo, etc. Ninguno de estos se aplica a las lambdas.

Supongo que la mayoría de los usos de los AIC eran para proporcionar implementaciones sin estado de funciones únicas y, por lo tanto, pueden reemplazarse con expresiones lambda, pero hay otros usos de AIC para los que no se pueden usar lambdas. Los AIC llegaron para quedarse.

ACTUALIZAR

Otra diferencia entre las AIC y las expresiones lambda es que las AIC introducen un nuevo alcance. Es decir, los nombres se resuelven a partir de las superclases e interfaces de AIC y pueden ocultar nombres que se producen en el entorno que los encierra léxicamente. Para lambdas, todos los nombres se resuelven léxicamente.

Stuart Marks
fuente
1
Las lambdas pueden tener estado. En este sentido, no veo ninguna diferencia entre Lambdas y AIC.
nosid
1
Los AIC de @nosid, al igual que las instancias de cualquier clase, pueden mantener el estado en los campos, y este estado es accesible (y potencialmente mutable por) cualquier método de la clase. Este estado existe hasta que el objeto es GC, es decir, tiene una extensión indefinida, por lo que puede persistir en todas las llamadas a métodos. El único estado con extensión indefinida que tienen las lambdas se captura en el momento en que se encuentra la lambda; este estado es inmutable. Las variables locales dentro de una lambda son mutables, pero existen solo mientras una invocación lambda está en curso.
Stuart Marks
1
@nosid Ah, el truco de matriz de un solo elemento. Simplemente no intente utilizar su contador de varios hilos. Si va a asignar algo en el montón y capturarlo en una lambda, también puede usar un AIC y agregar un campo que pueda mutar directamente. Usar una lambda de esta manera puede funcionar, pero ¿por qué molestarse cuando puede usar un objeto real?
Stuart Marks
2
AIC creará un archivo similar a este, A$1.classpero Lambda no lo hará. ¿Puedo agregar esto en Diferencia?
Asif Mushtaq
2
@UnKnown Eso es principalmente una preocupación de implementación; no afecta cómo se programa con AICs vs lambdas, que es de lo que trata principalmente esta pregunta. Tenga en cuenta que una expresión lambda genera una clase con un nombre como LambdaClass$$Lambda$1/1078694789. Sin embargo, esta clase es generada sobre la marcha por la metafábrica lambda, no por javac, por lo que no hay un .classarchivo correspondiente . Una vez más, sin embargo, esta es una preocupación de implementación.
Stuart Marks
60

Lambdas, aunque es una gran característica, solo funcionará con tipos SAM. Es decir, interfaces con un único método abstracto. Fallará tan pronto como su interfaz contenga más de 1 método abstracto. Ahí es donde las clases anónimas serán útiles.

Entonces, no, no podemos simplemente ignorar las clases anónimas. Y solo para su información, su sort()método puede simplificarse más, omitiendo la declaración de tipo para p1y p2:

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

También puede utilizar la referencia de método aquí. O agrega un compareByFirstName()método en la Personclase y usa:

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

o, agregue un getter para firstName, obtenga directamente el método Comparatorfrom Comparator.comparing():

Collections.sort(personList, Comparator.comparing(Person::getFirstName));
Rohit Jain
fuente
4
Lo sabía, pero prefiero el largo en términos de legibilidad, porque de lo contrario podría resultar confuso averiguar de dónde provienen estas variables.
Amin Abu-Taleb
@ AminAbu-Taleb ¿Por qué sería confuso? Esa es una sintaxis Lambda válida. Los tipos se infieren de todos modos. De todos modos, es una elección personal. Puede dar tipos explícitos. Sin problemas.
Rohit Jain
1
Hay otra diferencia sutil entre lambdas y clases anónimas: las clases anónimas se pueden anotar directamente con las nuevas anotaciones de tipo Java 8 , como por ejemplo new @MyTypeAnnotation SomeInterface(){};. Esto no es posible para las expresiones lambda. Para obtener más detalles, consulte mi pregunta aquí: Anotar la interfaz funcional de una expresión Lambda .
Balder
36

Rendimiento Lambda con clases anónimas

Cuando se lanza la aplicación, se debe cargar y verificar cada archivo de clase.

Las clases anónimas son procesadas por el compilador como un nuevo subtipo para la clase o interfaz dada, por lo que se generará un nuevo archivo de clase para cada una.

Las lambdas son diferentes en la generación de códigos de bytes, son más eficientes, utilizan la instrucción dinámica invocada que viene con JDK7.

Para Lambdas, esta instrucción se utiliza para retrasar la traducción de la expresión lambda en código de bytes hasta el tiempo de ejecución. (la instrucción se invocará solo por primera vez)

Como resultado, la expresión Lambda se convertirá en un método estático (creado en tiempo de ejecución). (Hay una pequeña diferencia con los casos stateles y statefull, se resuelven mediante argumentos de método generados)

Dmitriy Kuzkin
fuente
Cada lambda también necesita una nueva clase, pero se genera en tiempo de ejecución, por lo que en este sentido, las lambdas no son más eficientes que las clases anónimas. Las lambdas se crean a través de las invokedynamiccuales generalmente son más lentas que las invokespecialutilizadas para crear nuevas instancias de clases anónimas. Entonces, en este sentido, las lambdas también son más lentas (sin embargo, JVM puede optimizar las invokedynamicllamadas la mayor parte del tiempo).
ZhekaKozlov
3
@AndreiTomashpolskiy 1. Sea cortés. 2. Lea este comentario de un ingeniero de compilación: habrahabr.ru/post/313350/comments/#comment_9885460
ZhekaKozlov
@ZhekaKozlov, no necesita ser un ingeniero de compilación para leer el código fuente de JRE y usar javap / debugger. Lo que te falta es que la generación de una clase contenedora para un método lambda se realiza completamente en memoria y cuesta casi nada, mientras que crear una instancia de AIC implica resolver y cargar el recurso de clase correspondiente (lo que significa llamada al sistema de E / S). Por lo tanto, la invokedynamicgeneración de clases ad-hoc es increíblemente rápida en comparación con las clases anónimas compiladas.
Andrei Tomashpolskiy
@AndreiTomashpolskiy I / O no es necesariamente lento
ZhekaKozlov
14

Existen las siguientes diferencias:

1) Sintaxis

Las expresiones lambda se ven ordenadas en comparación con la clase interna anónima (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. Alcance

Una clase interna anónima es una clase, lo que significa que tiene un ámbito para la variable definida dentro de la clase interna.

Mientras, expresión lambda no es un ámbito en sí mismo, sino que es parte del ámbito adjunto.

Se aplica una regla similar para super y esta palabra clave cuando se usa dentro de una clase interna anónima y una expresión lambda. En el caso de una clase interna anónima, esta palabra clave se refiere al ámbito local y la palabra clave super se refiere a la superclase de la clase anónima. Mientras que en el caso de la expresión lambda, esta palabra clave se refiere al objeto del tipo envolvente y super se referirá a la superclase de la clase envolvente.

//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) Rendimiento

En tiempo de ejecución, las clases internas anónimas requieren carga de clases, asignación de memoria e inicialización de objetos e invocación de un método no estático, mientras que la expresión lambda es pura actividad en tiempo de compilación y no genera costos adicionales durante el tiempo de ejecución. Entonces, el rendimiento de la expresión lambda es mejor en comparación con las clases internas anónimas. **

** Me doy cuenta de que este punto no es del todo cierto. Consulte la siguiente pregunta para obtener más detalles. Lambda vs rendimiento de clase interna anónima: ¿reducir la carga en ClassLoader?

atom217
fuente
3

Lambda en Java 8 se introdujo para la programación funcional. Donde puede evitar el código repetitivo. Encontré este interesante artículo sobre lambda.

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

Es recomendable utilizar funciones lambda para lógicas simples. Si implementar lógica compleja usando lambdas será una sobrecarga en la depuración del código en caso de problema.

Tim
fuente
0

La clase anónima está ahí para quedarse porque lambda es buena para funciones con métodos abstractos únicos, pero para todos los demás casos, las clases internas anónimas son su salvador.

spidy
fuente
-1
  • La sintaxis lambda no requiere escribir el código obvio que Java puede inferir.
  • Al usar invoke dynamic, lambda no se vuelve a convertir a las clases anónimas durante el tiempo de compilación (Java no tiene que pasar por la creación de objetos, solo se preocupa por la firma del método, puede unirse al método sin crear un objeto
  • lambda pone más énfasis en lo que queremos hacer en lugar de lo que tenemos que hacer antes de poder hacerlo
MagGGG
fuente