2010-05-10 15 views
20

W Javie anonimowa klasa wewnętrzna może odnosić się do zmiennych w to lokalny zakres:W jaki sposób java implementuje zamykanie klas wewnętrznych?

public class A { 
    public void method() { 
     final int i = 0; 

     doStuff(new Action() { 
      public void doAction() { 
       Console.printf(i); // or whatever 
      } 
     }); 
    } 
} 

Moje pytanie brzmi jak to jest faktycznie realizowane? Jak uzyskać i dostać się do anonimowej wewnętrznej implementacji doAction i dlaczego musi to być final?

Odpowiedz

11

Kompilator automatycznie generuje konstruktor dla twojej anonimowej klasy wewnętrznej i przekazuje lokalną zmienną do tego konstruktora.

Konstruktor zapisuje tę wartość w zmiennej klasy (pole) o nazwie i, która będzie używana wewnątrz "zamknięcia".

Dlaczego musi być ostateczny? Więc przyjrzyjmy się sytuacji w przypadku gdy nie jest:

public class A { 
    public void method() { 
     int i = 0; // note: this is WRONG code 

     doStuff(new Action() { 
      public void doAction() { 
       Console.printf(i); // or whatever 
      } 
     }); 

     i = 4; // A 
     // B 
     i = 5; // C 
    } 
} 

W sytuacji A pole i z Action musi również zostać zmieniony, załóżmy to możliwe: potrzebuje odniesienie do obiektu Action.

Załóżmy, że w sytuacji B ten przypadek Action jest zbierany śmieci.

Teraz w sytuacji C: potrzebuje instancji Action, aby zaktualizować zmienną klasy, ale jej wartość to GCed. Musi "wiedzieć", że jest GCed, ale to jest trudne.

Aby ułatwić wdrażanie maszyny wirtualnej, projektanci języka Java powiedzieli, że powinna ona być ostateczna, tak aby maszyna wirtualna nie potrzebowała sposobu sprawdzenia, czy obiekt zniknął, i zagwarantować, że zmienna nie jest zmodyfikowane i że maszyna wirtualna lub kompilator nie musi utrzymywać odniesienia do wszystkich zastosowań zmiennej wewnątrz anonimowych klas wewnętrznych i ich instancji.

+0

W rzeczywistości zmienna syntezowana, która zawiera kopię zmiennej, nie ma nazwy i. W zależności od wersji używanego kompilatora to "$ i" lub "+ i". –

15

Zmienne lokalne są (oczywiście) nie są udostępniane między różnymi metodami, takimi jak method() i doAction() powyżej. Ale ponieważ jest on ostateczny, nic złego się nie stanie w tym przypadku, więc język nadal pozwala na to. Jednak kompilator musi zrobić coś inteligentnego w tej sytuacji. Pozwala spojrzeć na to, co javac produkuje:

$ javap -v "A\$1"   # A$1 is the anonymous Action-class. 
... 
final int val$i; // A field to store the i-value in. 

final A this$0;  // A reference to the "enclosing" A-object. 

A$1(A, int); // created constructor of the anonymous class 
    Code: 
    Stack=2, Locals=3, Args_size=3 
    0: aload_0 
    1: aload_1 
    2: putfield #1; //Field this$0:LA; 
    5: aload_0 
    6: iload_2 
    7: putfield #2; //Field val$i:I 
    10: aload_0 
    11: invokespecial #3; //Method java/lang/Object."<init>":()V 
    14: return 
    ... 
public void doAction(); 
    Code: 
    Stack=2, Locals=1, Args_size=1 
    0: getstatiC#4; //Field java/lang/System.out:Ljava/io/PrintStream; 
    3: aload_0 
    4: getfield #2; //Field val$i:I 
    7: invokevirtual #5; //Method java/io/PrintStream.println:(I)V 
    10: return 

To rzeczywiście pokazuje, że

  • okazało zmienną i w polu,
  • stworzony konstruktor dla anonimowego klasy, który przyjął odwołanie do obiektu A obiektu
  • , do którego później uzyskano dostęp w metodzie doAction().

(Na marginesie:. Musiałem zainicjować zmienną new java.util.Random().nextInt() aby zapobiec jego optymalizacji dala dużo kodu)


Podobny dyskusja tutaj

method local innerclasses accessing the local variables of the method

+2

Nie ma nic (wiele) do czynienia z gwintowaniem. To tylko efekt uboczny. Niezła klęska java, btw: daje świetny wgląd w kompilator. – Pindatjuh

+0

Masz rację. Sprawdzę. Dzięki za wskaźnik. – aioobe

+0

@Pindatjuh, Zaktualizowano dezaprobatę ... zdałem sobie sprawę, że zoptymalizował wiele kodu, ponieważ kompilator zdał sobie sprawę, że 'i' zawsze był 0. – aioobe

3

Lokalna instancja klasy (klasa anonimowa) musi utrzymywać oddzielną kopię zmiennej, ponieważ może ona przeżyć tę funkcję. Aby nie pomylić dwóch modyfikowalnych zmiennych o tej samej nazwie w tym samym zakresie, zmienna musi być ostateczna.

Aby uzyskać więcej informacji, patrz Java Final - an enduring mystery.

Powiązane problemy