Dlaczego tylko ostateczne zmienne są dostępne w anonimowej klasie?

  1. a tutaj może być tylko ostateczna. Dlaczego? Jak mogę przypisać a w metodzie onClick(), nie zachowując jej jako członka prywatnego?

    private void f(Button b, final int a){
        b.addClickHandler(new ClickHandler() {
    
            @Override
            public void onClick(ClickEvent event) {
                int b = a*5;
    
            }
        });
    }
    
  2. Jak mogę zwrócić 5 * a Po kliknięciu?

    private void f(Button b, final int a){
        b.addClickHandler(new ClickHandler() {
    
            @Override
            public void onClick(ClickEvent event) {
                 int b = a*5;
                 return b; // but return type is void 
            }
        });
    }
    
Author: Andrii Abramov, 2011-01-19

12 answers

Jak zauważono w komentarzach, niektóre z nich stają się nieistotne w Java 8, gdzie final może być niejawne. Tylko efektywnie końcowa zmienna może być użyta w anonimowej klasie wewnętrznej lub wyrażeniu lambda.


Wynika to zasadniczo ze sposobu, w jaki Java zarządza zamknięciami .

Kiedy tworzysz instancję anonimowej klasy wewnętrznej, wszystkie zmienne, które są używane w tej klasie, mają swoje wartości skopiowane przez autogenerowany konstruktor. Pozwala to uniknąć kompilator musi automatycznie generować różne dodatkowe typy, aby utrzymać stan logiczny "zmiennych lokalnych", tak jak robi to na przykład kompilator C#... (Gdy C# przechwytuje zmienną w funkcji anonimowej, tak naprawdę przechwytuje zmienną - zamknięcie może zaktualizować zmienną w sposób, który jest postrzegany przez główny korpus metody i odwrotnie.)

Ponieważ wartość została skopiowana do instancji anonimowej klasy wewnętrznej, wyglądałoby to dziwnie, gdyby zmienna mogła zostać zmodyfikowana przez resztę metoda-możesz mieć kod, który zdawał się działać ze zmienną nieaktualną (ponieważ faktycznie tak będzie ... będziesz pracował z kopią wykonaną w innym czasie). Podobnie, jeśli możesz wprowadzić zmiany wewnątrz anonimowej klasy wewnętrznej, deweloperzy mogą oczekiwać, że zmiany te będą widoczne w ciele metody zamykającej.

Uczynienie zmiennej finalnym usuwa wszystkie te możliwości - ponieważ wartości nie można zmienić w ogóle, nie musisz martwić się, czy takie zmiany będą widoczne. Jedynym sposobem na to, aby metoda i anonimowa Klasa wewnętrzna mogły widzieć siebie nawzajem, jest użycie mutowalnego typu jakiegoś opisu. Może to być sama klasa, tablica, zmienny Typ wrappera... cokolwiek w tym stylu. Zasadniczo jest to trochę jak komunikacja między jedną metodą a inną: zmiany wprowadzone do parametrów jednej metody nie są widziane przez jej wywołującego, ale zmiany wprowadzone do obiektów , o których mowa przez parametry są widoczne.

Jeśli interesuje Cię bardziej szczegółowe porównanie Javy i C#, mam Artykuł , który idzie do niego dalej. Chciałem skupić się na stronie Java w tej odpowiedzi:)

 438
Author: Jon Skeet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-04 08:00:59

Istnieje sztuczka, która pozwala klasie anonymous aktualizować dane w zewnętrznym zakresie.

private void f(Button b, final int a) {
    final int[] res = new int[1];
    b.addClickHandler(new ClickHandler() {
        @Override
        public void onClick(ClickEvent event) {
            res[0] = a * 5;
        }
    });

    // But at this point handler is most likely not executed yet!
    // How should we now res[0] is ready?
}

Jednak ta sztuczka nie jest zbyt dobra ze względu na problemy z synchronizacją. Jeśli handler jest wywoływany później, musisz 1) zsynchronizować dostęp do res, jeśli handler został wywołany z innego wątku 2) musisz mieć jakąś flagę lub wskazanie, że res został zaktualizowany

Ta sztuczka działa dobrze, jeśli Klasa anonymous jest wywoływana w tym samym wątku natychmiast. Like:

// ...

final int[] res = new int[1];
Runnable r = new Runnable() { public void run() { res[0] = 123; } };
r.run();
System.out.println(res[0]);

// ...
 40
Author: Ivan Dubrov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-14 09:29:36

Klasa anonimowa jest klasą wewnętrzną i ścisła reguła dotyczy klas wewnętrznych (JLS 8.1.3):

Każda zmienna lokalna, parametr metody formalnej lub parametr obsługi wyjątków użyta, ale nie zadeklarowana w klasie wewnętrznej musi być zadeklarowana jako końcowa. Każda zmienna lokalna, używana, ale nie zadeklarowana w klasie wewnętrznej musi być definitywnie przypisana przed ciałem klasy wewnętrznej .

Nie znalazłem powodu ani wyjaśnienia na jls lub jvms jeszcze, ale wiemy, że kompilator tworzy osobny plik klasy dla każdej klasy wewnętrznej i musi się upewnić, że metody zadeklarowane w tym pliku klasy (na poziomie kodu bajtowego) przynajmniej mają dostęp do wartości zmiennych lokalnych.

(Jon ma kompletną odpowiedź - tę zachowałem, ponieważ ktoś może zainteresować się zasadą JLS)

 16
Author: Andreas_D,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:10:46

Możesz utworzyć zmienną poziomu klasy, aby uzyskać zwracaną wartość. Znaczy

class A {
    int k = 0;
    private void f(Button b, int a){
        b.addClickHandler(new ClickHandler() {
        @Override
        public void onClick(ClickEvent event) {
            k = a * 5;
        }
    });
}

Teraz możesz uzyskać wartość K i używać jej tam, gdzie chcesz.

Odpowiedź twojego dlaczego brzmi:

Lokalna instancja klasy wewnętrznej jest powiązana z klasą główną i może uzyskać dostęp do końcowych zmiennych lokalnych jej metody zawierającej. Gdy instancja używa ostatecznej lokalnej metody zawierającej, zmienna zachowuje wartość przechowywaną w momencie tworzenia instancji, nawet jeśli zmienna została wyłączona zakresu (jest to w rzeczywistości prymitywna, limitowana wersja Javy).

Ponieważ lokalna Klasa wewnętrzna nie jest członkiem klasy ani pakietu, nie jest deklarowana z poziomem dostępu. (Być jednak jasne, że jego członkowie mają poziomy dostępu jak w normalnej klasie.)

 10
Author: Ashfak Balooch,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-14 09:30:13

Cóż, w Javie zmienna może być ostateczna nie tylko jako parametr, ale jako pole poziomu klasy, jak

public class Test
{
 public final int a = 3;

Lub jako zmienna lokalna, jak

public static void main(String[] args)
{
 final int a = 3;

Jeśli chcesz uzyskać dostęp i zmodyfikować zmienną z anonimowej klasy, możesz zmienić zmienną na poziomie klasy w klasie obejmującej.

public class Test
{
 public int a;
 public void doSomething()
 {
  Runnable runnable =
   new Runnable()
   {
    public void run()
    {
     System.out.println(a);
     a = a+1;
    }
   };
 }
}

Nie możesz mieć zmiennej jako ostatecznej i nadaj jej nową wartość. final oznacza po prostu, że: wartość jest niezmienna i finał

A ponieważ jest ostateczna, Java może bezpiecznie skopiować do lokalnych anonimowych klas. Nie dostaniesz jakiegośodniesienia do int (zwłaszcza, że nie możesz mieć odniesień do prymitywów takich jak int w Javie, tylko odniesienia doobiektów ).

To tylko kopiuje wartość a do niejawnej int zwanej a w Twojej anonimowej klasie.

 7
Author: Zach L,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-19 07:22:33

Powodem, dla którego dostęp został ograniczony tylko do lokalnych zmiennych końcowych jest to, że jeśli wszystkie zmienne lokalne zostaną udostępnione, najpierw muszą być skopiowane do oddzielnej sekcji, gdzie wewnętrzne klasy mogą mieć do nich dostęp, a utrzymywanie wielu kopii zmiennych zmiennych lokalnych może prowadzić do niespójności danych. Natomiast zmienne końcowe są niezmienne i dlatego dowolna liczba kopii do nich nie będzie miała żadnego wpływu na spójność danych.

 5
Author: Swapnil Gangrade,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-09-01 05:41:03

Metody wewnątrz anonimowej klasy wewnętrznej mogą być wywoływane również po zakończeniu wątku, który ją wywołał. W twoim przykładzie Klasa wewnętrzna będzie wywoływana w wątku wysyłania zdarzeń, a nie w tym samym wątku, który ją utworzył. Stąd zakres zmiennych będzie różny. Aby więc chronić takie problemy z zakresem przypisywania zmiennych, musisz zadeklarować je jako ostateczne.

 2
Author: S73417H,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-19 07:12:53

Gdy anonimowa Klasa wewnętrzna jest zdefiniowana w ciele metody, wszystkie zmienne zadeklarowane jako końcowe w zakresie tej metody są dostępne z wewnątrz klasy wewnętrznej. Dla wartości skalarnych, po jej przypisaniu, wartość zmiennej końcowej nie może się zmienić. Dla wartości obiektu odniesienie nie może się zmienić. Pozwala to kompilatorowi Javy "przechwytywać" wartość zmiennej w czasie wykonywania i przechowywać kopię jako pole w klasie wewnętrznej. Po zakończeniu zewnętrznej metody i jej stosu ramka została usunięta, oryginalna zmienna zniknęła, ale prywatna Kopia klasy wewnętrznej pozostaje we własnej pamięci klasy.

(http://en.wikipedia.org/wiki/Final_%28Java%29 )

 2
Author: ola_star,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-02-17 12:20:06
private void f(Button b, final int a[]) {

    b.addClickHandler(new ClickHandler() {

        @Override
        public void onClick(ClickEvent event) {
            a[0] = a[0] * 5;

        }
    });
}
 1
Author: Bruce Zu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-28 19:38:34

Ponieważ Jon ma odpowiedź na szczegóły implementacji inną możliwą odpowiedzią byłoby to, że JVM nie chce obsługiwać zapisu w rekordzie, który zakończył jego aktywację.

Rozważ przypadek użycia, w którym Twoje lambdy zamiast być stosowane, są przechowywane w jakimś miejscu i uruchamiane później.

Pamiętam, że w Smalltalku można dostać nielegalny sklep podniesiony, gdy robisz taką modyfikację.

 0
Author: mathk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-24 13:41:55

Wypróbuj ten kod,

Utwórz listę tablic i umieść w niej wartość i zwróć ją:

private ArrayList f(Button b, final int a)
{
    final ArrayList al = new ArrayList();
    b.addClickHandler(new ClickHandler() {

         @Override
        public void onClick(ClickEvent event) {
             int b = a*5;
             al.add(b);
        }
    });
    return al;
}
 0
Author: Wasim,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-18 11:00:55

Maybe this trick gives u an idea

Boolean var= new anonymousClass(){
    private String myVar; //String for example
    @Overriden public Boolean method(int i){
          //use myVar and i
    }
    public String setVar(String var){myVar=var; return this;} //Returns self instane
}.setVar("Hello").method(3);
 -2
Author: Whimusical,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-28 01:37:44