2016-02-04 33 views
5

Ta self-answered question została zainspirowana przez Variable 'snackbar' might not have been initialized. Czułem, że jest więcej szczegółów, które lepiej byłoby dodać oddzielnie od tego konkretnego pytania."Zmienny przykład nie został zainicjowany" w anonimowej klasie

Dlaczego następującego kodu nie można skompilować?

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 
     } 
    }; 
    } 
} 

błąd kompilacji:

error: variable example might not have been initialized 

Odpowiedz

8

Dzieje się tak ze względu na sposób, że anonimowe klasy są wdrażane. Można to zobaczyć, jeśli się nieznaczne zmiany w kodzie, a następnie dekompilować:

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

to zrobić anonimowy klasy odnoszą się do innej zmiennej lokalnej. To teraz się skompiluje; możemy dekompilować użyciu javap i zobaczyć interfejs anonimowej klasy:

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

(Example$1 jest imię, którym Java wewnętrznie odnosi się do anonimowej klasy).

Pokazuje to, że kompilator dodał konstruktor do anonimowej klasy, która przyjmuje parametr Runnable; ma również pole o nazwie val$other. Nazwa tego pola powinna wskazywać, że to pole jest powiązane ze zmienną lokalną other.

Można kopać do kodu bajtowego dalej i zobaczyć, że parametr ten jest przypisany do val$other:

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 

Więc, co pokazuje to jest sposób, anonimowe klas uzyskać dostęp do zmiennych z ich zakresem załączając: są po prostu przekazał wartość w czasie budowy.

To powinno pokazać, dlaczego kompilator powstrzymuje cię przed napisaniem kodu takiego jak ten w pytaniu: aby móc go skonstruować, musi przekazać odwołanie do Runnable anonimowej klasie. Jednak sposób, że Java ocenia następujący kod:

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

jest, aby w pełni ocenić prawą stronę, a potem przypisać ją do zmiennej po lewej stronie. Jednak potrzebuje wartość zmiennej po prawej stronie, aby przejść do wygenerowanego konstruktora Runnable$1:

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

To example nie została wcześniej zadeklarowana nie jest problemem, ponieważ kod ten jest semantycznie identyczne:

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

więc błąd, że nie można dostać to, że zmienna nie może być rozwiązany - jednak example nie została przypisana wartość, zanim zostanie użyta jako argument do konstruktora, stąd błąd kompilatora.


Można twierdzić, że jest to po prostu realizacja szczegół: to nie powinno mieć znaczenia, że ​​argument musi być przekazany do konstruktora, ponieważ nie ma możliwości, że metoda run() można powoływać się przed kontrolą zadanie.

Właściwie, to nie jest prawda: można powołać run() przed przypisaniem, co następuje:

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

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

Jeśli powołując się example wewnątrz anonimowej klasy pozwolono, byłbyś w stanie to napisać. Dlatego odwołanie się do tej zmiennej jest niedozwolone.

+0

bardzo miło! Posiadanie zdekompilowanej klasy 'Example $ 1' pokazuje, że _why_ powinien" inny "być" końcowy ". Gdyby tak nie było, "Przykład 1 $" mógł potencjalnie zajmować się przestarzałą kopią "innego", co byłoby ... dziwne, delikatnie mówiąc. –

4

Można użyć "to", aby uniknąć błędu kompilacji:

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