2014-12-22 7 views
9

Considerare seguenti classi:Perché le espressioni lambda in Java 8 richiedono variabili utilizzate al suo interno per utilizzare il modificatore "finale", ma non quando si utilizza il riferimento al metodo?

class Foo<T> { 

    void handle(T t) { 
     System.out.println("handling " + t); 
    } 

    public static void main(String[] args) { 
     List<Integer> list = new ArrayList<>(); 
     list.add(1); 
     list.add(2); 

     Foo<Integer> f = new Foo<>(); 

     list.forEach(f::handle);    // compiles fine 
     //list.forEach(obj -> f.handle(obj));// compilation error 

     f = new Foo<>(); // reassign f 

    } 
} 

Perché ottengo errore di compilazione per obj -> f.handle(obj), ma non per f::handle?

+5

Um, entrambe le versioni _should_ funzionano senza il modificatore 'final'. (Tuttavia, deve essere ancora efficacemente definitivo.) –

+0

Sì, la domanda era: perché la seconda versione non richiede il modificatore finale. –

+7

_Nessuna versione dovrebbe richiedere la finale. –

risposta

14

Questi sono due diversi costrutti che stanno facendo due cose diverse. Nel primo caso, si ottiene il riferimento al metodo di un oggetto specifico: questo deve essere fatto una sola volta, dopo di che la JVM ha il proprio riferimento (così efficacemente finale) all'oggetto f e può chiamare il metodo handle. Nel secondo caso, ad ogni chiamata la JVM deve risolvere il riferimento f e si lamenta quindi che f deve essere final. È possibile scrivere facilmente il codice che imposta f su null mentre lo forEach è in esecuzione e quindi causa un NPE.

+0

Ho fatto un piccolo esempio che potrebbe evidenziare la differenza tra espressione lambda e riferimento al metodo: http://pastebin.com/4W0ivi7U – vbezhenar

+0

Si potrebbe anche scrivere 'finale Foo f = null;' e causare un NPE. Questo non spiega perché 'f' debba essere effettivamente definitivo. – zeroflagL

+0

Qualcuno ha appena cambiato la mia intera domanda, quindi non so se stai parlando della "sua" domanda o mia. Ma penso di aver capito il tuo punto, grazie. –

3

Per aggiungere un'illustrazione per la risposta di Giovanni, siamo in grado di mettere in evidenza la differenza tra f::handle e obj -> f.handle(obj) se sostituiamo f con una chiamata di metodo:

static Set<String> f() { 
    System.out.println(" f() called"); 
    return new HashSet<>(); 
} 

public static void main(String[] args) { 
    List<String> empty = Collections.emptyList(); 
    List<String> strings = Arrays.asList("foo", "bar"); 

    System.out.println("method reference, no invocations"); 
    empty.forEach(f()::add); 

    System.out.println("method reference, 2 invocations"); 
    strings.forEach(f()::add); 

    System.out.println("lambda, no invocations"); 
    empty.forEach(str -> f().add(str)); 

    System.out.println("lambda, 2 invocations"); 
    strings.forEach(str -> f().add(str)); 
} 

uscita:

method reference, no invocations 
    f() called 
method reference, 2 invocations 
    f() called 
lambda, no invocations 
lambda, 2 invocations 
    f() called 
    f() called 

Quindi, come si vedere .forEach(f()::add) valuterà immediatamente f() e quindi chiama il numero add(...) sul risultato tante volte quante chiamate lambda vengono chiamate.

D'altra parte, str -> f().add(str) non farà nulla in anticipo ma chiamerà f() ogni volta che viene richiamato il lambda.

Problemi correlati