2015-08-28 9 views
5

Bitte erklären Sie mir, dass wie Lambda-Ausdruck Instanzvariable seiner umschließenden Klasse verwenden und ändern kann und nur lokale Variable des umgebenden Bereichs verwenden kann (außer es ist final oder effektiv final)? Meine grundlegende Frage ist, wie Instanzvariable der Klasse innerhalb Lambda modifizierbar ist und lokale Variable ist nicht im Zusammenhang mit der AnwendungsbereichLambda-Ausdruck und Variable Capture

+2

Da Lambdas effektiv eine anonyme innere Klasse sind, ist dies ein mögliches Duplikat von [Warum sind nur endgültige Variablen in der anonymen Klasse zugänglich?] (Http://stackoverflow.com/questions/ 4732544/why-sind-nur-letzte-Variablen-barrierefrei-in-anonymer-Klasse) – mthmulders

+1

Dies gilt für Java 8, aber soweit ich weiß, ist es nicht angegeben, also könnte dies in zukünftigen Versionen von Java geändert werden. – Alex

+3

nur 'this' wird erfasst. Alle Zugriffe auf eine Instanzvariable innerhalb des Lambda-Körpers erfolgen über 'this.field', daher können Sie auch darauf schreiben. – ZhongYu

Antwort

8

Zunächst können wir einen Blick auf die JLS, nehmen Sie die folgende Zustände an:

Alle verwendeten lokalen Variablen, Formalparameter oder Ausnahmeparameter, die in einem Lambda-Ausdruck nicht deklariert sind, müssen entweder als final deklariert werden oder als endgültig gelten (§4.12.4), oder es tritt ein Kompilierungsfehler auf, wenn die Verwendung versucht wird.

Jede lokale Variable, die in einem Lambda-Body verwendet, aber nicht deklariert wurde, muss eindeutig vor dem Lambda-Body zugewiesen werden (§16 (Definite Assignment)), oder es tritt ein Fehler bei der Kompilierung auf.

Ähnliche Regeln zum variablen Gebrauch gelten im Körper einer inneren Klasse (§8.1.3). Die Beschränkung auf effektiv endgültige Variablen verbietet den Zugriff auf sich dynamisch ändernde lokale Variablen, deren Erfassung wahrscheinlich Parallelitätsprobleme einführen würde. Verglichen mit der endgültigen Einschränkung reduziert es die bürokratische Belastung der Programmierer.

Die Beschränkung auf effektiv endgültige Variablen enthält Standard-Loop-Variablen, aber keine Enhanced-for-Loop-Variablen, die für jede Iteration der Schleife als unterschiedlich behandelt werden (§14.14.2).


Um es besser zu verstehen, in diesem Beispiel Klasse einen Blick:

public class LambdaTest { 

    public static void main(String[] args) { 
     LambdaTest test = new LambdaTest(); 
     test.returnConsumer().accept("Hello"); 
     test.returnConsumerWithInstanceVariable().accept("Hello"); 
     test.returnConsumerWithLocalFinalVariable().accept("Hello"); 
    } 

    String string = " world!"; 

    Consumer<String> returnConsumer() { 
     return ((s) -> {System.out.println(s);}); 
    } 

    Consumer<String> returnConsumerWithInstanceVariable() { 
     return ((s) -> {System.out.println(s + string);}); 
    } 

    Consumer<String> returnConsumerWithLocalFinalVariable() { 
     final String foo = " you there!"; 
     return ((s) -> {System.out.println(s + foo);}); 
    } 

} 

Ausgabe der Haupt

Hello 
Hello world! 
Hello you there! 

ist Dies liegt daran, Rückkehr hier eine Lambda groß ist die Genauso wie das Erstellen einer neuen anonymen Klasse mit . Ihr Lambda - eine Instanz von Consumer<String> hat einen Verweis auf die Klasse, in der es erstellt wurde. Sie könnten die returnConsumerWithInstanceVariable schreiben, um System.out.println(s + LambdaTest.this.string) zu verwenden, dies würde genau dasselbe tun. Aus diesem Grund können Sie auf Instanzvariablen zugreifen (und diese ändern).

Wenn Sie in Ihrer Methode eine (effektive) letzte lokale Variable haben, können Sie darauf zugreifen, weil Sie darauf zugreifen können, weil sie in Ihre Lambda-Instanz kopiert wird.

Aber jedoch, wenn es nicht endgültig, was denken Sie in folgendem Zusammenhang passieren soll:

Consumer<String> returnConsumerBad() { 
    String foo = " you there!"; 
    Consumer<String> results = ((s) -> {System.out.println(s + foo);}); 
    foo = " to all of you!"; 
    return results; 
} 

der Wert auf Ihre Instanz erhält kopiert soll, aber dann nicht aktualisiert, wenn die lokale Variable ist aktualisiert? Das würde wahrscheinlich Verwirrung stiften, da ich glaube, dass viele Programmierer erwarten würden, den neuen Wert "an euch alle" zu haben, nachdem sie dieses Lambda zurückgegeben haben.

Wenn Sie einen primitiven Wert hätten, würde er auf dem Stapel liegen. Sie können also nicht einfach auf die lokale Variable verweisen, da sie nach dem Ende Ihrer Methode verschwinden könnte.

0

Sie können diesen Artikel beziehen - https://www.infoq.com/articles/Java-8-Lambdas-A-Peek-Under-the-Hood erklärt auf Lambda-Ausdrücke Kompilierung.Wie erläutert, werden Lambda-Ausdrücke/Codebausteine ​​in die anonyme Klasse kompiliert. Diese anonymen Klassen werden mit dem Namensformat (<<Enclosing Class name>>$<<1(Number)>>) kompiliert. Nehmen wir also an, wenn nicht-finale lokale Variablen zulässig sind, kann der Compiler nicht nachvollziehen, woher diese lokale Variable stammt als anonyme Klasse. .class-Dateien werden mit dem oben genannten Format separat erstellt/kompiliert wie normale Java-Klassen.

Wenn also die lokale Variable endgültig ist, erstellt der Compiler eine letzte Instanz in der anoymous-Klasse, die dem Compiler keine Mehrdeutigkeit verleiht. Weitere Informationen finden Sie in der obigen Link-Verknüpfung