2016-02-04 22 views
5

Diese self-answered question wurde von Variable 'snackbar' might not have been initialized inspiriert. Ich dachte, dass es mehr Details gäbe, die besser getrennt von dieser spezifischen Frage hinzugefügt werden könnten."Variablenbeispiel wurde möglicherweise nicht initialisiert" in anonymer Klasse

Warum kann der folgende Code nicht kompiliert werden?

public class Example { 
    public static void main(String[] args) { 
    final Runnable example = new Runnable() { 
     @Override 
     public void run() { 
     System.out.println(example); // Error on this line 
     } 
    }; 
    } 
} 

Übersetzungsfehler:

error: variable example might not have been initialized 

Antwort

8

Dies geschieht wegen der Art, dass anonyme Klassen implementiert sind. Sie können dies sehen, wenn Sie eine geringfügige Änderung am Code vornehmen und dann decompile:

final Runnable other = null; 
    final Runnable example = new Runnable() { 
     @Override 
     public void run() { 
     System.out.println(other); 
     } 
    }; 

das heißt, die anonyme Klasse auf eine andere lokale Variable verweisen machen. Dies wird jetzt kompilieren; wir können mit javap dekompilieren und die Schnittstelle der anonymen Klasse sehen:

final class Example$1 implements java.lang.Runnable { 
    final java.lang.Runnable val$other; 
    Example$1(java.lang.Runnable); 
    public void run(); 
} 

(Example$1 ist der Name, mit dem Java intern auf die anonyme Klasse bezieht).

Dies zeigt, dass der Compiler der anonymen Klasse einen Konstruktor hinzugefügt hat, der einen Runnable Parameter verwendet; Es hat auch ein Feld namens val$other. Dieser Name dieses Felds sollte darauf hinweisen, dass dieses Feld mit der lokalen other-Variablen verknüpft ist.

Sie können weiter in die Bytecode graben, und sehen, dass dieser Parameter auf val$other zugeordnet:

Example$1(java.lang.Runnable); 
    Code: 
     0: aload_0 
     // This gets the parameter... 
     1: aload_1 
     // ...and this assigns it to the field val$other 
     2: putfield  #1     // Field val$other:Ljava/lang/Runnable; 
     5: aload_0 
     6: invokespecial #2     // Method java/lang/Object."<init>":()V 
     9: return 

Also, was dies zeigt, ist die Art und Weise, dass anonyme Klassen die Variablen von ihrem umgebenden Gültigkeitsbereich zuzugreifen: sie sind einfach den Wert zur Konstruktionszeit übergeben.

Dies sollte hoffentlich zeigen, warum der Compiler Sie davon abhält, Code wie den in der Frage zu schreiben: Er muss in der Lage sein, den Verweis auf Runnable an die anonyme Klasse zu übergeben, um ihn zu konstruieren. Doch die Art und Weise, dass Java den folgenden Code auswertet:

final Runnable example = new Runnable() { ... } 

ist vollständig mit der rechten Seite zuerst bewerten, und weisen Sie dann die Variable auf der linken Seite. Allerdings muss er den Wert der Variablen auf der rechten Seite, um in den erzeugten Konstruktor Runnable$1 passieren:

final Runnable example = new Example$1(example); 

Das example wurde bisher nicht erklärt worden ist das kein Problem, da dieser Code semantisch identisch mit:

final Runnable example; 
example = new Example$1(example); 

so dass der Fehler, die Sie erhalten, ist nicht, dass die Variable nicht aufgelöst werden kann - jedoch hat example kein Wert zugewiesen wurde, bevor es als Argument für den Konstruktor verwendet wird, damit die Compilerfehler


Es könnte argumentiert werden, dass dieses Detail einfach eine Implementierung ist: Es sollte keine Rolle spielen, dass das Argument in den Konstruktor übergeben werden muss, da es keine Möglichkeit gibt, dass die run() Verfahren den Stand aufgerufen werden können Zuordnung.

Eigentlich ist das nicht wahr: Sie run() vor der Zuweisung aufrufen kann, wie folgt:

final Runnable example = new Runnable() { 
    Runnable runAndReturn() { 
    run(); 
    return this; 
    } 

    @Override public void run() { 
    System.out.println(example); 
    } 
}.runAndReturn(); 

Wenn Bezug auf example innerhalb der anonymen Klasse erlaubt, würden Sie in der Lage sein, dies zu schreiben. Daher ist die Bezugnahme auf diese Variable nicht zulässig.

+0

Really nice! Wenn die dekompilierte Klasse "Example $ 1" ziemlich genau zeigt, warum _ '' '' 'final sein soll. Wenn dies nicht der Fall wäre, könnte das "Beispiel $ 1" möglicherweise mit einer veralteten Kopie von "Anderes" zu tun haben, was ... seltsam wäre, um es gelinde auszudrücken. –

4

Sie können "dieses" verwenden, um die Kompilierung-Fehler zu vermeiden:

final Runnable example = new Runnable() { 
    @Override 
    public void run() { 
    System.out.println(this); // Use "this" on this line 
    } 
}; 
Verwandte Themen