Gram z lambdami w Javie 8 i natknąłem się na ostrzeżenie local variables referenced from a lambda expression must be final or effectively final
. Wiem, że kiedy używam zmiennych wewnątrz anonimowej klasy, muszą one być ostateczne w klasie zewnętrznej, ale nadal - jaka jest różnica między ostateczną a efektywną ostateczną ?
350
Odpowiedzi:
Załóżmy na przykład, że zmienna
numberLength
nie jest zadeklarowana jako ostateczna i dodajesz zaznaczoną instrukcję przypisania wPhoneNumber
konstruktorze:Z powodu tej instrukcji przypisania zmienna numberLength nie jest już efektywnie ostateczna. W rezultacie kompilator Java generuje komunikat o błędzie podobny do „zmiennych lokalnych przywoływanych z klasy wewnętrznej musi być ostateczny lub faktycznie końcowy”, gdy klasa wewnętrzna PhoneNumber próbuje uzyskać dostęp do zmiennej numberLength:
http://codeinventions.blogspot.in/2014/07/difference-between-final-and.html
http://docs.oracle.com/javase/tutorial/java/javaOO/localclasses.html
źródło
numberLength
stała się zmienną lokalną tej metody.Uważam, że najprostszym sposobem na wyjaśnienie „skutecznie ostatecznego” jest wyobrażenie sobie dodania
final
modyfikatora do deklaracji zmiennej. Jeśli przy tej zmianie program nadal będzie zachowywał się w ten sam sposób, zarówno w czasie kompilacji, jak i w czasie wykonywania, wówczas ta zmienna jest faktycznie ostateczna.źródło
case k
wymaga stałego wyrażenia, które może być zmienną stałą („Zmienna stała jest zmienną końcową pierwotnego typu lub typu Łańcuch, który jest inicjowany stałym wyrażeniem” JLS 4.12.4 ), co jest szczególnym przypadkiem ostatecznego zmienna.Według dokumentów :
Zasadniczo, jeśli kompilator znajdzie zmienną, która nie pojawia się w zadaniach poza jej inicjalizacją, wówczas zmienną uważa się za faktycznie ostateczną .
Rozważmy na przykład klasę:
źródło
bar
w twoim przykładzie nie ma zmiennej lokalnej, ale pole. „Skutecznie końcowy” w komunikacie o błędzie, jak powyżej, w ogóle nie dotyczy pól.bar
jest tutaj parametrem, a nie polem.„Skutecznie końcowy” to zmienna, która nie dawałaby błędu kompilatora, gdyby miał być dołączony przez „końcowy”
Z artykułu Briana Goetza:
finał stanu lambda - Brian Goetz
źródło
Ta zmienna poniżej jest ostateczna , więc nie możemy zmienić jej wartości po zainicjowaniu. Jeśli spróbujemy, otrzymamy błąd kompilacji ...
Ale jeśli stworzymy taką zmienną, możemy zmienić jej wartość ...
Ale w Javie 8 wszystkie zmienne są domyślnie ostateczne . Ale istnienie drugiej linii w kodzie sprawia, że nie jest ona ostateczna . Jeśli więc usuniemy 2. wiersz z powyższego kodu, nasza zmienna będzie teraz „efektywnie końcowa” ...
Więc .. Każda zmienna, która jest przypisana tylko raz, jest „efektywnie ostateczna” .
źródło
Zmienna jest ostateczna lub faktycznie ostateczna, gdy jest inicjowana raz i nigdy nie jest mutowana w swojej klasie właściciela. I nie możemy zainicjować go w pętlach lub klasach wewnętrznych .
Końcowe :
Skutecznie końcowy :
źródło
Gdy wyrażenie lambda używa przypisanej zmiennej lokalnej z otaczającej jej przestrzeni, istnieje ważne ograniczenie. Wyrażenie lambda może wykorzystywać tylko zmienną lokalną, której wartość się nie zmienia. To ograniczenie określa się jako „ przechwytywanie zmiennych ”, które jest opisane jako; wartości przechwytywania wyrażenia lambda, a nie zmienne .
Zmienne lokalne, których może użyć wyrażenie lambda, są znane jako „ efektywnie końcowe ”.
Skutecznie zmienna końcowa to taka, której wartość nie zmienia się po pierwszym przypisaniu. Nie ma potrzeby jawnego deklarowania takiej zmiennej jako końcowej, chociaż takie działanie nie byłoby błędem.
Zobaczmy to na przykładzie: mamy lokalną zmienną i, która jest inicjowana wartością 7, przy czym w wyrażeniu lambda próbujemy zmienić tę wartość, przypisując nową wartość do i. Spowoduje to błąd kompilatora - „ Zmienna lokalna i zdefiniowana w zakresie obejmującym musi być ostateczna lub faktycznie ostateczna ”
źródło
Efektywny temat końcowy opisano w JLS 4.12.4, a ostatni akapit zawiera jasne wyjaśnienie:
źródło
final to zmienna deklarowana ze słowem kluczowym
final
, przykład:pozostaje
final
przez cały program.efektywnie końcowy : dowolna zmienna lokalna lub parametr, któremu w tej chwili przypisano wartość tylko raz (lub zaktualizowano tylko raz). W trakcie programu może nie pozostać efektywnie ostateczny . oznacza to zatem, że efektywnie zmienna końcowa może utracić swoją efektywną właściwość końcową natychmiast po tym, jak zostanie przypisana / zaktualizowana co najmniej o jeszcze jedno przypisanie. przykład:
źródło
final
słowa kluczowego do deklaracji bez wprowadzenia błędów kompilacji, to nie jest to ostatecznie ostateczne . Jest to przeciwieństwo tego stwierdzenia: „Jeśli zmienna jest faktycznie końcowa, dodanie końcowego modyfikatora do jej deklaracji nie spowoduje żadnych błędów w czasie kompilacji”.Jak powiedzieli inni, zmienna lub parametr, którego wartość nigdy nie ulega zmianie po zainicjowaniu, jest faktycznie ostateczny. W powyższym kodzie, jeśli zmienisz wartość
x
klasy wewnętrznej,FirstLevel
kompilator wyświetli komunikat o błędzie:źródło
Dostęp do wyrażeń lambda
zmienne statyczne,
zmienne instancji,
skutecznie końcowe parametry metody, oraz
skutecznie końcowe zmienne lokalne.
Źródło: OCP: Oracle Certified Professional Java SE 8 Programmer II Study Guide, Jeanne Boyarsky, Scott Selikoff
Dodatkowo,
Źródło: Zaczynając od Java: Od struktur kontrolnych poprzez obiekty (wydanie 6), Tony Gaddis
Ponadto nie zapomnij o znaczeniu tego
final
, że jest on inicjowany dokładnie raz, zanim zostanie użyty po raz pierwszy.źródło
Zadeklarowanie zmiennej
final
lub jej nie deklarowaniefinal
, ale utrzymanie jej w ostateczności może spowodować (w zależności od kompilatora) inny kod bajtowy.Rzućmy okiem na mały przykład:
Odpowiedni kod bajtowy
main
metody (Java 8u161 w systemie Windows w wersji 64-bitowej):Odpowiednia tabela numerów linii:
Jak widzimy w liniach kodu źródłowego
12
,13
,14
nie pojawia się w kodzie bajtów. To dlatego, żei
jesttrue
i nie zmieni swojego stanu. Dlatego ten kod jest nieosiągalny (więcej w tej odpowiedzi ). Z tego samego powodu9
brakuje też kodu w linii . Stani
nie musi być oceniany, ponieważ jesttrue
na pewno.Z drugiej strony, chociaż zmienna
j
jest faktycznie ostateczna , nie jest przetwarzana w ten sam sposób. Nie zastosowano takich optymalizacji. Stanj
ocenia się dwa razy. Kod bajtowy jest taki sam, niezależnie od tego, czyj
jest faktycznie ostateczny .źródło
Nie zaczęło się to na Javie 8, używam tego od dawna. Ten kod używał (przed java 8), aby być legalnym:
źródło
final
zmiennych, ale w Javie 8 również te, które są faktycznie ostateczne.