a
może być tylko ostateczny tutaj. Dlaczego? W jaki sposób można przypisaća
wonClick()
sposób bez zachowania go jako prywatnego członka?private void f(Button b, final int a){ b.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { int b = a*5; } }); }
Jak mogę zwrócić
5 * a
po kliknięciu? Mam na myśli,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 } }); }
java
event-handling
anonymous-class
Andrii Abramov
źródło
źródło
Odpowiedzi:
Jak zauważono w komentarzach, niektóre z nich stają się nieistotne w Javie 8, gdzie
final
może być niejawne. Tylko skutecznie zmienna końcowy może być stosowany w anonimowej klasy wewnętrznej lub lambda wyrażenia chociaż.Zasadniczo wynika to ze sposobu, w jaki Java zarządza zamknięciami .
Kiedy tworzysz instancję anonimowej klasy wewnętrznej, wszystkie zmienne używane w tej klasie mają swoje wartości kopiowane za pomocą automatycznie generowanego konstruktora. Dzięki temu kompilator nie musi automatycznie generować różnych dodatkowych typów w celu utrzymania stanu logicznego „zmiennych lokalnych”, jak na przykład kompilator C # ... (Gdy C # przechwytuje zmienną w funkcji anonimowej, naprawdę przechwytuje zmienną - zamknięcie może zaktualizować zmienną w sposób widoczny dla głównej części 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ę metody - możesz mieć kod, który wydaje się działać z nieaktualną zmienną ( bo tak faktycznie by się działo ... pracowałbyś z kopią wykonaną w innym czasie). Podobnie, jeśli możesz wprowadzić zmiany w anonimowej klasie wewnętrznej, programiści mogą oczekiwać, że zmiany te będą widoczne w treści metody zamykającej.
Ostateczne zakończenie zmiennej usuwa wszystkie te możliwości - ponieważ wartości w ogóle nie można zmienić, nie musisz się martwić, czy takie zmiany będą widoczne. Jedynym sposobem, aby pozwolić metodzie i anonimowej klasie wewnętrznej zobaczyć się nawzajem, jest użycie zmiennego typu opisu. Może to być sama klasa zamykająca, tablica, zmienny typ opakowania ... cokolwiek takiego. Zasadniczo jest to trochę jak komunikacja między jedną metodą: zmiany dokonane w parametrach jednej metody nie są widoczne dla jej obiektu wywołującego, ale zmiany dokonane w obiektach, do których odnoszą się parametry, są widoczne.
Jeśli interesuje Cię bardziej szczegółowe porównanie między zamknięciami Java i C #, mam artykuł, który omawia to bardziej szczegółowo. W tej odpowiedzi chciałem skupić się na stronie Java :)
źródło
final
(ale nadal musi być skuteczny).Istnieje sztuczka, która pozwala anonimowej klasie aktualizować dane w zakresie zewnętrznym.
Jednak ta sztuczka nie jest zbyt dobra z powodu problemów z synchronizacją. Jeśli program obsługi zostanie wywołany później, musisz 1) zsynchronizować dostęp do res, jeśli program obsługi został wywołany z innego wątku 2) musi mieć jakąś flagę lub informację, że res został zaktualizowany
Ta sztuczka działa OK, jeśli anonimowa klasa zostanie natychmiast wywołana w tym samym wątku. Lubić:
źródło
List.forEach
kod jest bezpieczny.Klasa anonimowa to klasa wewnętrzna, a do klas wewnętrznych obowiązuje ścisła reguła (JLS 8.1.3) :
Nie znalazłem jeszcze powodu ani wyjaśnienia na temat plików jls lub jvms, 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ź - nie usuwam tego, ponieważ ktoś może być zainteresowany zasadą JLS)
źródło
Możesz utworzyć zmienną na poziomie klasy, aby uzyskać zwracaną wartość. mam na myśli
teraz możesz uzyskać wartość K i używać jej tam, gdzie chcesz.
Odpowiedź na pytanie dlaczego:
Lokalna instancja klasy wewnętrznej jest powiązana z klasą główną i może uzyskać dostęp do końcowych zmiennych lokalnych swojej metody zawierającej. Gdy instancja korzysta z ostatecznego ustawienia lokalnego swojej metody zawierającej, zmienna zachowuje wartość, którą trzymała w momencie jej tworzenia, nawet jeśli zmienna wykroczyła poza zakres (jest to w rzeczywistości surowa, ograniczona wersja zamknięć Javy).
Ponieważ lokalna klasa wewnętrzna nie jest członkiem klasy ani pakietu, nie jest zadeklarowana z poziomem dostępu. (Wyjaśnij jednak, że jego członkowie mają poziomy dostępu jak w normalnej klasie).
źródło
Cóż, w Javie zmienna może być ostateczna nie tylko jako parametr, ale jako pole na poziomie klasy
lub jako zmienna lokalna, taka jak
Jeśli chcesz uzyskać dostęp do zmiennej z klasy anonimowej i ją zmodyfikować, możesz ustawić zmienną na poziomie klasy w klasie zamykającej .
Nie możesz mieć zmiennej jako ostatecznej i nadać jej nową wartość.
final
oznacza po prostu, że: wartość jest niezmienna i ostateczna.A ponieważ jest ostateczny, Java może bezpiecznie skopiować go do lokalnych anonimowych klas. Nie dostajesz żadnych odniesień do int (zwłaszcza, że nie możesz mieć odniesień do prymitywów takich jak int w Javie, tylko odniesienia do Objects ).
Po prostu kopiuje wartość a do ukrytego int zwanego a w twojej anonimowej klasie.
źródło
static
. Być może bardziej zrozumiałe jest użycie „zmiennej instancji”.Powód, dla którego dostęp został ograniczony tylko do lokalnych zmiennych końcowych, jest taki, że jeśli wszystkie zmienne lokalne zostałyby udostępnione, najpierw musiałyby zostać skopiowane do osobnej sekcji, w której klasy wewnętrzne mogą mieć do nich dostęp i utrzymywać wiele kopii zmienne zmienne lokalne mogą prowadzić do niespójności danych. Natomiast zmienne końcowe są niezmienne, a zatem dowolna liczba ich kopii nie będzie miała żadnego wpływu na spójność danych.
źródło
Int
jest przeniesiony do wewnątrz zamknięcia, zmień tę zmienną na instancjęIntRef
(zasadniczo zmiennegoInteger
opakowania). Każdy dostęp do zmiennej jest następnie odpowiednio przepisywany.Aby zrozumieć uzasadnienie tego ograniczenia, rozważ następujący program:
InterfaceInstance pozostaje w pamięci po initialize deklaracji metody, ale parametr val nie. JVM nie może uzyskać dostępu do zmiennej lokalnej poza swoim zakresem, więc Java wykonuje kolejne wywołanie printInteger , kopiując wartość val do niejawnego pola o tej samej nazwie w interfaceInstance . InterfaceInstance mówi się, że schwytany wartość parametru lokalnej. Jeśli parametr nie był końcowy (lub faktycznie końcowy), jego wartość mogłaby się zmienić, niesynchronizując się z przechwyconą wartością, potencjalnie powodując nieintuicyjne zachowanie.
źródło
Metody w ramach anonimowej klasy wewnętrznej mogą być wywoływane długo po zakończeniu wątku, który je stworzył. W twoim przykładzie klasa wewnętrzna zostanie wywołana w wątku wysyłki zdarzeń, a nie w tym samym wątku, co ten, który ją utworzył. W związku z tym zakres zmiennych będzie inny. Aby więc chronić takie problemy z zakresem przypisywania zmiennych, musisz je zadeklarować jako ostateczne.
źródło
Kiedy anonimowa klasa wewnętrzna jest zdefiniowana w ciele metody, wszystkie zmienne zadeklarowane jako ostateczne w zakresie tej metody są dostępne z klasy wewnętrznej. W przypadku wartości skalarnych po przypisaniu wartość końcowej zmiennej nie może się zmienić. W przypadku wartości obiektu odwołanie nie może się zmienić. Pozwala to kompilatorowi Java na „przechwytywanie” wartości zmiennej w czasie wykonywania i przechowywanie kopii jako pola w klasie wewnętrznej. Po zakończeniu metody zewnętrznej i usunięciu ramki stosu oryginalna zmienna zniknęła, ale prywatna kopia klasy wewnętrznej pozostaje w pamięci klasy.
( http://en.wikipedia.org/wiki/Final_%28Java%29 )
źródło
źródło
Ponieważ Jon ma odpowiedź dotyczącą szczegółów 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 lambdas zamiast się stosować, są przechowywane w pewnym miejscu i biegną później.
Pamiętam, że w Smalltalk można uzyskać nielegalny sklep, gdy zrobisz taką modyfikację.
źródło
Wypróbuj ten kod,
Utwórz listę macierzy, umieść w niej wartość i zwróć ją:
źródło
Anonimowa klasa Java jest bardzo podobna do zamknięcia Javascript, ale Java implementuje to w inny sposób. (sprawdź odpowiedź Andersena)
Aby nie mylić programisty Java z dziwnym zachowaniem, które może wystąpić u osób pochodzących z tła Javascript. Chyba dlatego zmuszają nas do korzystania
final
, to nie jest ograniczenie JVM.Spójrzmy na poniższy przykład Javascript:
W języku Javascript
counter
wartość będzie wynosić 100, ponieważ jest tylko jednacounter
od początku do końca zmienna.Ale w Javie, jeśli nie
final
, zostanie wydrukowany0
, ponieważ podczas tworzenia obiektu wewnętrznego0
wartość jest kopiowana do ukrytych właściwości obiektu klasy wewnętrznej. (tutaj są dwie zmienne całkowite, jedna w metodzie lokalnej, druga w ukrytych właściwościach klasy wewnętrznej)Zatem wszelkie zmiany po utworzeniu wewnętrznego obiektu (jak linia 1) nie wpłyną na wewnętrzny obiekt. Spowoduje to zamieszanie między dwoma różnymi wynikami i zachowaniem (między Javą a Javascriptem).
Uważam, że właśnie dlatego Java postanawia wymusić, aby był ostateczny, więc dane są „spójne” od początku do końca.
źródło
Ostateczna zmienna Java wewnątrz klasy wewnętrznej
klasa wewnętrzna może korzystać tylko
Object
...)int
typ wartości (pierwotny) (np. ...) może być zawinięty przez końcowy typ odwołania.IntelliJ IDEA
może pomóc ukryć to w tablicy jednowymiarowejGdy kompilator generuje a
non static nested
(inner class
) [About] - nowa klasa -<OuterClass>$<InnerClass>.class
tworzona jest związana parametry i przekazywane do konstruktora [zmienna lokalna na stosie] . Jest podobny do zamknięciazmienna końcowa jest zmienną, której nie można ponownie przypisać. ostatnia zmienna odniesienia wciąż może być zmieniona poprzez modyfikację stanu
Możliwe, że to będzie dziwne, ponieważ jako programista możesz to zrobić
źródło
Może ta sztuczka daje ci pomysł
źródło