Java8 Lambdas vs classi anonime


111

Dato che Java8 è stato recentemente rilasciato e le sue nuovissime espressioni lambda sembrano davvero interessanti, mi chiedevo se questo significasse la fine delle classi Anonymous a cui eravamo così abituati.

Ho cercato un po 'su questo e ho trovato alcuni esempi interessanti su come le espressioni Lambda sostituiranno sistematicamente quelle classi, come il metodo di ordinamento Collection, che utilizzava per ottenere un'istanza anonima di Comparator per eseguire l'ordinamento:

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

Ora può essere fatto usando Lambdas:

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

E sembra sorprendentemente conciso. Quindi la mia domanda è: c'è qualche motivo per continuare a utilizzare quelle classi in Java8 invece di Lambdas?

MODIFICARE

Stessa domanda ma nella direzione opposta, quali sono i vantaggi dell'utilizzo di Lambdas anziché di classi Anonymous, poiché Lambdas può essere utilizzato solo con interfacce a metodo singolo, questa nuova funzionalità è solo una scorciatoia utilizzata solo in pochi casi o è davvero utile?


5
Certo, per tutte quelle classi anonime che forniscono metodi con effetti collaterali.
tobias_k

11
Solo per tua informazione, puoi anche costruire il comparatore come:, Comparator.comparing(Person::getFirstName)se getFirstName()fosse un metodo di ritorno firstName.
skiwi

1
O classi anonime con più metodi, o ...
Mark Rotteveel

1
Sono tentato di votare per chiudere in quanto troppo ampio, soprattutto a causa delle domande aggiuntive dopo EDIT .
Mark Rotteveel

1
Un bell'articolo di approfondimento su questo argomento: infoq.com/articles/Java-8-Lambdas-A-Peek-Under-the-Hood
Ram Patra

Risposte:


108

Una classe interna anonima (AIC) può essere utilizzata per creare una sottoclasse di una classe astratta o di una classe concreta. Un'AIC può anche fornire un'implementazione concreta di un'interfaccia, inclusa l'aggiunta di stati (campi). È possibile fare riferimento a un'istanza di un AIC utilizzando thisnei suoi corpi del metodo, quindi è possibile richiamare ulteriori metodi, il suo stato può essere mutato nel tempo, ecc. Nessuno di questi si applica ai lambda.

Immagino che la maggior parte degli usi degli AIC fosse quello di fornire implementazioni senza stato di singole funzioni e quindi può essere sostituito con espressioni lambda, ma ci sono altri usi di AIC per i quali non è possibile utilizzare lambda. Gli AIC sono qui per restare.

AGGIORNARE

Un'altra differenza tra gli AIC e le espressioni lambda è che gli AIC introducono un nuovo ambito. Cioè, i nomi vengono risolti dalle superclassi e dalle interfacce dell'AIC e possono nascondere i nomi che si verificano nell'ambiente che racchiude lessicalmente. Per i lambda, tutti i nomi vengono risolti lessicalmente.


1
Lambdas può avere stato. A questo proposito, non vedo alcuna differenza tra Lambdas e AIC.
nosid

1
Gli AIC @nosid, come le istanze di qualsiasi classe, possono contenere lo stato nei campi e questo stato è accessibile a (e potenzialmente modificabile da) qualsiasi metodo della classe. Questo stato esiste fino a quando l'oggetto non è GC, ovvero ha un'estensione indefinita, quindi può persistere tra le chiamate di metodo. L'unico stato con lambda di estensione indefinita viene acquisito nel momento in cui viene rilevato il lambda; questo stato è immutabile. Le variabili locali all'interno di un lambda sono modificabili, ma esistono solo mentre è in corso una chiamata lambda.
Stuart Marks

1
@nosid Ah, l'hack dell'array a elemento singolo. Basta non provare a utilizzare il contatore da più thread. Se hai intenzione di allocare qualcosa sull'heap e catturarlo in un lambda, potresti anche usare un AIC e aggiungere un campo che puoi modificare direttamente. Usare un lambda in questo modo può funzionare, ma perché preoccuparsi quando puoi usare un oggetto reale?
Stuart Marks

2
AIC creerà un file simile a questo, A$1.classma Lambda no. Posso aggiungerlo in differenza?
Asif Mushtaq,

2
@UnKnown Questo è principalmente un problema di implementazione; non influisce sul modo in cui si programma con AIC vs lambda, che è ciò di cui si occupa principalmente questa domanda. Tieni presente che un'espressione lambda genera una classe con un nome simile a LambdaClass$$Lambda$1/1078694789. Tuttavia, questa classe viene generata al volo dalla metafactory lambda, non da javac, quindi non esiste un .classfile corrispondente . Anche in questo caso, tuttavia, si tratta di un problema di implementazione.
Stuart Marks

60

Anche se Lambda è un'ottima funzionalità, funziona solo con i tipi SAM. Cioè, si interfaccia con un solo metodo astratto. Non funzionerebbe non appena la tua interfaccia contiene più di 1 metodo astratto. È qui che le classi anonime saranno utili.

Quindi, no, non possiamo semplicemente ignorare le classi anonime. E solo per tua informazione, il tuo sort()metodo può essere più semplificato, saltando la dichiarazione del tipo per p1e p2:

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

Puoi anche usare il riferimento al metodo qui. O aggiungi un compareByFirstName()metodo in Personclasse e usi:

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

oppure, aggiungi un getter per firstName, ottieni direttamente il metodo Comparatorfrom Comparator.comparing():

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

4
Lo sapevo, ma preferisco quello lungo in termini di leggibilità, perché altrimenti potrebbe creare confusione scoprire da dove provengono queste variabili.
Amin Abu-Taleb

@ AminAbu-Taleb Perché dovrebbe creare confusione. Questa è una sintassi Lambda valida. I tipi sono comunque dedotti. Comunque è una scelta personale. Puoi dare tipi espliciti. Senza problemi.
Rohit Jain

1
C'è un'altra sottile differenza tra lambda e classi anonime: le classi anonime possono essere annotate direttamente con le nuove annotazioni di tipo Java 8 , come ad es new @MyTypeAnnotation SomeInterface(){};. Questo non è possibile per le espressioni lambda. Per i dettagli, vedere la mia domanda qui: Annotazione dell'interfaccia funzionale di un'espressione Lambda .
Balder

36

Prestazioni Lambda con classi anonime

Quando l'applicazione viene avviata, ogni file di classe deve essere caricato e verificato.

Le classi anonime vengono elaborate dal compilatore come un nuovo sottotipo per la classe o l'interfaccia data, quindi verrà generato un nuovo file di classe per ciascuna.

I lambda sono diversi nella generazione del bytecode, sono più efficienti, utilizzano istruzioni dinamiche invocate fornite con JDK7.

Per Lambda questa istruzione viene utilizzata per ritardare la traduzione dell'espressione lambda in bytecode fino al runtime. (l'istruzione verrà richiamata solo per la prima volta)

Di conseguenza, l'espressione Lambda diventerà un metodo statico (creato in fase di esecuzione). (C'è una piccola differenza con stateles e statefull case, vengono risolti tramite argomenti del metodo generati)


Ogni lambda necessita anche di una nuova classe, ma viene generata in fase di esecuzione, quindi in questo senso le lambda non sono più efficienti delle classi anonime. I Lambda vengono creati tramite il invokedynamicquale è generalmente più lento di quello invokespecialutilizzato per creare nuove istanze di classi anonime. Quindi, in questo senso anche i lambda sono più lenti (tuttavia, JVM può ottimizzare le invokedynamicchiamate la maggior parte del tempo).
ZhekaKozlov

3
@AndreiTomashpolskiy 1. Sii gentile. 2. Leggi questo commento di un ingegnere compilatore: habrahabr.ru/post/313350/comments/#comment_9885460
ZhekaKozlov

@ZhekaKozlov, non è necessario essere un ingegnere compilatore per leggere il codice sorgente JRE e utilizzare javap / debugger. Quello che ti manca è che la generazione di una classe wrapper per un metodo lambda viene eseguita interamente in memoria e non costa quasi nulla, mentre l'istanza di un AIC implica la risoluzione e il caricamento della risorsa della classe corrispondente (che significa chiamata di sistema I / O). Quindi, invokedynamiccon la generazione di classi ad hoc è velocissima rispetto alle classi anonime compilate.
Andrei Tomashpolskiy

@AndreiTomashpolskiy I / O non è necessariamente lento
ZhekaKozlov

14

Ci sono le seguenti differenze:

1) Sintassi

Le espressioni lambda sembrano chiare rispetto a Anonymous Inner Class (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) Ambito di applicazione

Una classe interna anonima è una classe, il che significa che ha l'ambito per la variabile definita all'interno della classe interna.

Considerando che, l' espressione lambda non è un ambito a sé stante, ma fa parte dell'ambito di inclusione.

Una regola simile si applica per super e questa parola chiave quando si utilizza la classe interna anonima e l'espressione lambda. In caso di classe interna anonima, questa parola chiave si riferisce all'ambito locale e la parola chiave super si riferisce alla super classe della classe anonima. Mentre in caso di espressione lambda questa parola chiave si riferisce all'oggetto del tipo che lo racchiude e super farà riferimento alla super classe della classe che lo racchiude.

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

In fase di esecuzione le classi interne anonime richiedono il caricamento della classe, l'allocazione della memoria e l'inizializzazione degli oggetti e l'invocazione di un metodo non statico mentre l'espressione lambda è pura attività in fase di compilazione e non comporta costi aggiuntivi durante il runtime. Quindi le prestazioni dell'espressione lambda sono migliori rispetto alle classi interne anonime. **

** Mi rendo conto che questo punto non è del tutto vero. Fare riferimento alla seguente domanda per i dettagli. Lambda vs prestazioni anonime della classe interna: ridurre il carico sul ClassLoader?


3

Lambda in java 8 è stato introdotto per la programmazione funzionale. Dove puoi evitare il codice boilerplate. Mi sono imbattuto in questo interessante articolo sui lambda.

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

Si consiglia di utilizzare le funzioni lambda per logiche semplici. Se l'implementazione di una logica complessa utilizzando lambda sarà un sovraccarico nel debug del codice in caso di problemi.


0

Le classi anonime sono lì per restare perché lambda è utile per funzioni con singoli metodi astratti, ma per tutti gli altri casi le classi interne anonime sono il tuo salvatore.


-1
  • La sintassi lambda non richiede la scrittura del codice ovvio che Java può dedurre.
  • Usando invoke dynamic, lambda non vengono riconvertiti nelle classi anonime durante la compilazione (Java non deve passare attraverso la creazione di oggetti, si preoccupa solo della firma del metodo, può legarsi al metodo senza creare oggetti
  • lambda pone maggiormente l'accento su ciò che vogliamo fare invece di ciò che dobbiamo fare prima di poterlo fare
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.