Do tej pory uważałem, że ostatecznie ostateczne i ostateczne są mniej więcej równoważne i że JLS potraktuje je podobnie, jeśli nie identyczne w rzeczywistym zachowaniu. Potem znalazłem ten wymyślony scenariusz:
final int a = 97;
System.out.println(true ? a : 'c'); // outputs a
// versus
int a = 97;
System.out.println(true ? a : 'c'); // outputs 97
Najwyraźniej JLS robi ważną różnicę między tymi dwoma tutaj i nie jestem pewien, dlaczego.
Czytam inne wątki, takie jak
- Różnica między ostatecznym a faktycznie ostatecznym
- Efektywnie zmienna końcowa a zmienna końcowa
- Co oznacza, że zmienna jest „faktycznie ostateczna”?
ale nie wdają się w takie szczegóły. W końcu na szerszym poziomie wydają się być w dużym stopniu równoważne. Ale kopiąc głębiej, najwyraźniej się różnią.
Co powoduje to zachowanie, czy ktoś może podać definicje JLS, które to wyjaśniają?
Edycja: znalazłem inny powiązany scenariusz:
final String a = "a";
System.out.println(a + "b" == "ab"); // outputs true
// versus
String a = "a";
System.out.println(a + "b" == "ab"); // outputs false
Więc internowanie łańcucha również zachowuje się tutaj inaczej (nie chcę używać tego fragmentu kodu w prawdziwym kodzie, jestem tylko ciekawy innego zachowania).
źródło
byte
,short
lubchar
, a drugi argument jest stałą ekspresję typ,int
którego wartość można przedstawić w typie T , typ wyrażenia warunkowego to T. " --- Nawet znalazłem dokumentację Java 1.0 w Berkeley. Ten sam tekst . --- Tak, zawsze tak było.Odpowiedzi:
Przede wszystkim mówimy tylko o zmiennych lokalnych . Skutecznie ostateczna nie dotyczy pól. Jest to ważne, ponieważ semantyka
final
pól jest bardzo różna i podlega ciężkim optymalizacjom kompilatora i obietnicom modelu pamięci, patrz 17.5.1 na temat semantyki pól końcowych.Na poziomie powierzchniowym
final
ieffectively final
dla zmiennych lokalnych są rzeczywiście identyczne. Jednak JLS dokonuje wyraźnego rozróżnienia między nimi, co w rzeczywistości ma szeroki zakres efektów w specjalnych sytuacjach, takich jak ta.Przesłanka
Od JLS§4.12.4 o
final
zmiennych:Ponieważ
int
jest prymitywna, zmiennaa
jest taką stałą zmienną .Dalej, z tego samego rozdziału o
effectively final
:Ze sposobu sformułowania tego wynika jasno, że w drugim przykładzie
a
nie jest uważana za zmienną stałą, ponieważ nie jest ostateczna , a jedynie faktycznie ostateczna.Zachowanie
Teraz, gdy mamy rozróżnienie, spójrzmy, co się dzieje i dlaczego wynik jest inny.
Używasz operatora warunkowego
? :
tutaj , więc musimy sprawdzić jego definicję. Od JLS§15.25 :W tym przypadku mówimy o pliku liczbowych wyrażeniach warunkowych z JLS§15.25.2 :
I to jest ta część, w której te dwa przypadki są inaczej klasyfikowane.
skutecznie ostateczne
Wersja, do której
effectively final
pasuje ta reguła:Jest to takie samo zachowanie, jak gdybyś to zrobił
5 + 'd'
, tj. Wint + char
wyniku czegoint
. Zobacz JLS§5.6Więc wszystko jest promowane
int
tak, jaka
jestint
już. To wyjaśnia wydajność97
.finał
Wersja ze
final
zmienną jest dopasowana do tej reguły:Ostatnia zmienna
a
jest typuint
i wyrażeniem stałym (ponieważ jestfinal
). Można to przedstawić jakochar
, stąd wynik jest typowychar
. Na tym kończy się wynika
.Przykład ciągu
Przykład z równością ciągów opiera się na tej samej podstawowej różnicy,
final
zmienne są traktowane jako stałe wyrażenie / zmienna, aeffectively final
tak nie jest.Dlatego w Javie internowanie ciągów opiera się na stałych wyrażeniach
"a" + "b" + "c" == "abc"
jest
true
również (nie używaj tej konstrukcji w prawdziwym kodzie).Zobacz JLS §3.10.5 :
Łatwo przeoczyć, ponieważ mówi głównie o literałach, ale w rzeczywistości dotyczy to również stałych wyrażeń.
źródło
... ? a : 'c'
takiego samego zachowania, niezależnie od tego, czya
jest to zmienna, czy stała . Nie ma nic złego w tym wyrażeniu. --- W przeciwieństwie do tegoa + "b" == "ab"
jest złym wyrażeniem , ponieważ ciągi muszą być porównywane za pomocąequals()
( Jak porównać ciągi w Javie? ). Fakt, że działa on „przypadkowo”, gdya
jest stałą , jest tylko dziwactwem internalizacji literałów łańcuchowych."a" + "b" + "c" == "abc"
musi znajdować siętrue
w dowolnej prawidłowej implementacji języka Java.a + "b" == "ab"
nadal jest to błędne wyrażenie . Nawet jeśli ty wiesz, żea
jest to stała , jest zbyt podatna na błędy, aby nie wywoływaćequals()
. A może lepszym słowem jest kruchy , tj. Zbyt prawdopodobne, że rozpadnie się, gdy kod będzie utrzymywany w przyszłości.(final) String str = "a"; Stream.of(null, null). <Runnable>map( x -> () -> System.out.println(str)) .reduce((a,b) -> () -> System.out.println(a == b)) .ifPresent(Runnable::run);
zmienia swój wynik, gdystr
jest (nie)final
.Innym aspektem jest to, że jeśli zmienna jest zadeklarowana jako ostateczna w treści metody, zachowuje się inaczej niż końcowa zmienna przekazana jako parametr.
public void testFinalParameters(final String a, final String b) { System.out.println(a + b == "ab"); } ... testFinalParameters("a", "b"); // Prints false
podczas
public void testFinalVariable() { final String a = "a"; final String b = "b"; System.out.println(a + b == "ab"); // Prints true } ... testFinalVariable();
zdarza się, ponieważ kompilator wie, że korzystanie
final String a = "a"
za
zmienna będzie zawsze mają"a"
wartość tak, żea
i"a"
mogą być wymieniane bez problemów. Z drugiej strony, jeślia
nie jest zdefiniowanyfinal
lub jest zdefiniowany,final
ale jego wartość jest przypisywana w czasie wykonywania (jak w powyższym przykładzie, gdziea
parametr jest final ), kompilator nic nie wie przed jego użyciem. Zatem konkatenacja odbywa się w czasie wykonywania i generowany jest nowy ciąg, bez użycia puli stażystów.Zasadniczo zachowanie jest takie: jeśli kompilator wie, że zmienna jest stałą, może użyć jej tak samo, jak użycie stałej.
Jeśli zmienna nie jest zdefiniowana jako ostateczna (lub jest ostateczna, ale jej wartość jest definiowana w czasie wykonywania), nie ma powodu, aby kompilator traktował ją jako stałą, również jeśli jej wartość jest równa stałej, a jej wartość nigdy nie jest zmieniana.
źródło
finel
słowo kluczowe zastosowane do parametru, ma inną semantykę niżfinal
zastosowane do zmiennej lokalnej itp ...final String a; a = "a";
i uzyskać to samo zachowanie