W Javie widzimy wiele miejsc, w których final
można użyć słowa kluczowego, ale jego użycie jest rzadkie.
Na przykład:
String str = "abc";
System.out.println(str);
W powyższym przypadku str
może być, final
ale jest to zwykle pomijane.
Gdy metoda nigdy nie zostanie zastąpiona, możemy użyć końcowego słowa kluczowego. Podobnie w przypadku klasy, która nie będzie dziedziczona.
Czy użycie końcowego słowa kluczowego w którymkolwiek z powyższych przypadków naprawdę poprawia wydajność? Jeśli tak, to jak? Proszę wytłumacz. Jeśli właściwe użycie final
naprawdę ma znaczenie dla wydajności, jakie nawyki powinien rozwinąć programista Java, aby jak najlepiej wykorzystać słowo kluczowe?
Odpowiedzi:
Zwykle nie. W przypadku metod wirtualnych HotSpot śledzi, czy metoda została faktycznie zastąpiona, i jest w stanie przeprowadzić optymalizacje, takie jak inklinacja przy założeniu, że metoda nie została zastąpiona - dopóki nie załaduje klasy, która przesłania metodę, w którym to momencie może cofnąć (lub częściowo cofnąć) te optymalizacje.
(Oczywiście zakładamy, że używasz HotSpot - ale jest to zdecydowanie najpopularniejsza maszyna JVM, więc ...)
Moim zdaniem powinieneś używać w
final
oparciu o przejrzysty projekt i czytelność, a nie ze względu na wydajność. Jeśli chcesz coś zmienić ze względu na wydajność, powinieneś wykonać odpowiednie pomiary przed zgięciem najczystszego kodu z kształtu - w ten sposób możesz zdecydować, czy jakakolwiek dodatkowa wydajność jest warta gorszej czytelności / projektu. (Z mojego doświadczenia wynika, że prawie nigdy nie warto; YMMV.)EDYCJA: Jak już wspomniano o ostatnich polach, warto wspomnieć, że i tak często są dobrym pomysłem, jeśli chodzi o przejrzysty design. Zmieniają również gwarantowane zachowanie pod względem widoczności wątków: po zakończeniu działania konstruktora wszelkie pola końcowe są natychmiast widoczne w innych wątkach. Jest to prawdopodobnie najczęstsze zastosowanie
final
w moim doświadczeniu, chociaż jako zwolennik praktycznej reguły Josha Blocha dotyczącej „dziedziczenia lub zakazania”, prawdopodobnie powinienemfinal
częściej korzystać z zajęć ...źródło
final
jest generalnie zalecane, ponieważ ułatwia zrozumienie kodu i pomaga znaleźć błędy (ponieważ sprawia, że intencje programistów są wyraźne). PMD prawdopodobnie zaleca używanie zfinal
powodu tych problemów ze stylem, a nie ze względu na wydajność.Immutable classes are easier to design, implement, and use than mutable classes. They are less prone to error and are more secure.
. PonadtoAn immutable object can be in exactly one state, the state in which it was created.
vsMutable objects, on the other hand, can have arbitrarily complex state spaces.
. Z własnego doświadczenia wynika, że użycie słowa kluczowegofinal
powinno uwypuklić intencję programisty, by skłaniać się ku niezmienności, a nie „optymalizować” kod. Zachęcam do przeczytania tego rozdziału, fascynującego!final
słowa kluczowego w zmiennych może zmniejszyć ilość kodu bajtowego, co może mieć wpływ na wydajność.Krótka odpowiedź: nie martw się!
Długa odpowiedź:
Mówiąc o końcowych zmiennych lokalnych, należy pamiętać, że użycie słowa kluczowego
final
pomoże kompilatorowi zoptymalizować kod statycznie , co może w efekcie doprowadzić do szybszego kodu. Na przykład końcowe łańcuchya + b
w poniższym przykładzie są łączone statycznie (w czasie kompilacji).Wynik?
Testowane na Java Hotspot VM 1.7.0_45-b18.
Ile wynosi faktyczna poprawa wydajności? Nie śmiem powiedzieć. W większości przypadków prawdopodobnie marginalny (~ 270 nanosekund w tym teście syntetycznym, ponieważ w ogóle unika się konkatenacji łańcucha - rzadki przypadek), ale w wysoce zoptymalizowanym kodzie użytecznym może to być czynnik. W każdym razie odpowiedź na pierwotne pytanie brzmi „ tak”, może poprawić wydajność, ale w najlepszym razie nieznacznie .
Pomijając korzyści z czasu kompilacji, nie mogłem znaleźć żadnych dowodów na to, że użycie słowa kluczowego
final
ma jakikolwiek wymierny wpływ na wydajność.źródło
String
konkatenacja jest znacznie bardziej kosztowną operacją niż dodawanieIntegers
. Robienie tego statycznie (jeśli to możliwe) jest bardziej wydajne, tak pokazuje test.Tak, może. Oto przykład, w którym finał może zwiększyć wydajność:
Kompilacja warunkowa to technika, w której wiersze kodu nie są kompilowane do pliku klasy na podstawie określonego warunku. Można tego użyć do usunięcia ton kodu debugowania w kompilacji produkcyjnej.
rozważ następujące:
Konwertując atrybut doSomething na atrybut końcowy, powiedziałeś kompilatorowi, że ilekroć zobaczy on doSomething, powinien zastąpić go wartością false zgodnie z regułami podstawiania w czasie kompilacji. Pierwsze przejście kompilatora zmienia kod na mniej więcej taki:
Po wykonaniu tej czynności kompilator przyjrzy się jej ponownie i zobaczy, że w kodzie znajdują się nieosiągalne instrukcje. Ponieważ pracujesz z kompilatorem najwyższej jakości, nie lubi wszystkich tych nieosiągalnych kodów bajtów. Usuwa je i kończy się na tym:
zmniejszając w ten sposób wszelkie nadmierne kody lub niepotrzebne sprawdzanie warunkowe.
Edycja: Jako przykład weźmy następujący kod:
Podczas kompilacji tego kodu w Javie 8 i dekompilacji
javap -c Test.class
otrzymujemy:Możemy zauważyć, że skompilowany kod zawiera tylko nie-końcową zmienną
x
. Dowodzi to, że zmienne końcowe mają wpływ na wyniki, przynajmniej w tym prostym przypadku.źródło
Według IBM - nie dotyczy to klas ani metod.
http://www.ibm.com/developerworks/java/library/j-jtp04223.html
źródło
Dziwię się, że nikt tak naprawdę nie opublikował prawdziwego kodu, który jest dekompilowany, aby udowodnić, że istnieje co najmniej niewielka różnica.
Na odniesienie to zostało przetestowane na
javac
wersji8
,9
i10
.Załóżmy, że ta metoda:
Kompilacja tego kodu w obecnej postaci tworzy dokładnie taki sam kod bajtu, jak w przypadku
final
, gdy byłby obecny (final Object left = new Object();
).Ale ten:
Produkuje:
Pozostawanie
final
w obecności powoduje:Kod jest dość oczywisty, w przypadku gdy istnieje stała czasowa kompilacji, zostanie załadowana bezpośrednio na stos operandów (nie będzie przechowywana w tablicy zmiennych lokalnych, jak w poprzednim przykładzie
bipush 12; istore_0; iload_0
) - co to ma sens ponieważ nikt nie może tego zmienić.Z drugiej strony, dlaczego w drugim przypadku kompilator nie produkuje
istore_0 ... iload_0
jest poza mną, to nie jest tak, że ten slot0
jest używany w jakikolwiek sposób (może zmniejszać tablicę zmiennych w ten sposób, ale może brakować szczegółów wewnętrznych, nie mogę powiedz na pewno)Byłem zaskoczony widząc taką optymalizację, biorąc pod uwagę, jak małe są
javac
. Co powinniśmy zawsze używaćfinal
? Nie zamierzam nawet pisaćJMH
testu (który chciałem początkowo), jestem pewien, że diff jest w kolejnościns
(jeśli w ogóle można go przechwycić). Jedynym miejscem, w którym może to być problem, jest to, że metoda nie może zostać wstawiona ze względu na jej rozmiar (i zadeklarowaniefinal
zmniejszy ten rozmiar o kilka bajtów).Do rozwiązania są jeszcze dwa kolejne
final
. Po pierwsze, gdy metoda jestfinal
(zJIT
perspektywy), taka metoda jest monomorficzna - i są to najbardziej ukochane przezJVM
.Następnie są
final
zmienne instancji (które muszą być ustawione w każdym konstruktorze); są one ważne, ponieważ zagwarantują poprawne opublikowanie odniesienia, które zostało tu nieco zmienione, a także dokładnie określone przezJLS
.źródło
javac -g FinalTest.java
), zdekompilowałem kod za pomocąjavap -c FinalTest.class
i niefinal int left=12
uzyskałem tych samych wyników (z , mambipush 11; istore_0; bipush 12; istore_1; bipush 11; iload_1; iadd; ireturn
). Tak więc, wygenerowany kod bajtowy zależy od wielu czynników i trudno powiedzieć, czy mafinal
wpływ na wydajność, czy nie. Ale ponieważ kod bajtowy jest inny, mogą występować pewne różnice w wydajności.Naprawdę pytasz o dwa (przynajmniej) różne przypadki:
final
dla zmiennych lokalnychfinal
dla metod / klasJon Skeet już odpowiedział 2). O 1):
Nie sądzę, żeby miało to znaczenie; w przypadku zmiennych lokalnych kompilator może wywnioskować, czy zmienna jest ostateczna, czy nie (po prostu sprawdzając, czy przypisano ją więcej niż jeden raz). Jeśli więc kompilator chciał zoptymalizować zmienne przypisane tylko raz, może to zrobić bez względu na to, czy zmienna jest faktycznie zadeklarowana,
final
czy nie.final
moc mieć znaczenie dla pól klasy chronionej / publicznej; kompilatorowi bardzo trudno jest dowiedzieć się, czy pole jest ustawiane więcej niż jeden raz, ponieważ mogłoby się to zdarzyć z innej klasy (która mogła nawet nie zostać załadowana). Ale nawet wtedy JVM może skorzystać z techniki opisanej przez Jona (optymalizować optymistycznie, cofnąć, jeśli zostanie załadowana klasa, która zmienia pole).Podsumowując, nie widzę żadnego powodu, dla którego powinien on poprawić wydajność. Ten rodzaj mikrooptymalizacji raczej nie pomoże. Możesz spróbować go przetestować, aby się upewnić, ale wątpię, czy to coś zmieni.
Edytować:
W rzeczywistości, zgodnie z odpowiedzią Timo Westkämper,
final
może poprawić wydajność pól klasowych w niektórych przypadkach. Poprawiono mnie.źródło
final
, aby upewnić się, że nie przypisujesz jej dwukrotnie. O ile widzę, ta sama logika sprawdzania może być (i prawdopodobnie jest) używana do sprawdzania, czy zmienna może byćfinal
.javac
w lepszej optymalizacji. Ale to tylko spekulacje.final
zmienna jest pierwotnym typem lub typemString
i jest natychmiast przypisywana ze stałą czasową kompilacji, jak w przykładzie pytania, kompilator musi ją wstawić, ponieważ zmienna jest stałą czasową kompilacji dla specyfikacji. Jednak w większości przypadków kod może wyglądać inaczej, ale nadal nie ma znaczenia, czy stała jest wstawiana, czy odczytywana ze zmiennej lokalnej pod względem wydajności.Uwaga: nie jestem ekspertem od Java
Jeśli dobrze pamiętam moją Javę, nie byłoby sposobu na poprawienie wydajności przy użyciu końcowego słowa kluczowego. Zawsze wiedziałem, że istnieje dla „dobrego kodu” - projektu i czytelności.
źródło
Nie jestem ekspertem, ale przypuszczam, że powinieneś dodać
final
słowo kluczowe do klasy lub metody, jeśli nie zostanie ona nadpisana i pozostaw zmienne w spokoju. Jeśli będzie jakiś sposób na optymalizację takich rzeczy, kompilator zrobi to za Ciebie.źródło
Właściwie podczas testowania kodu związanego z OpenGL odkryłem, że użycie końcowego modyfikatora w polu prywatnym może obniżyć wydajność. Oto początek klasy, którą testowałem:
I to jest metoda, której użyłem do przetestowania wydajności różnych alternatyw, między innymi klasy ShaderInput:
Po trzeciej iteracji z rozgrzaną maszyną wirtualną konsekwentnie otrzymywałem te liczby bez ostatniego słowa kluczowego:
Z końcowym słowem kluczowym:
Zwróć uwagę na liczby dla testu ShaderInput.
Nie miało znaczenia, czy upubliczniłem pola.
Nawiasem mówiąc, jest jeszcze kilka dziwnych rzeczy. Klasa ShaderInput przewyższa wszystkie inne warianty, nawet w przypadku ostatniego słowa kluczowego. Jest to niezwykłe, ponieważ zasadniczo jest to klasa owijająca tablicę zmiennoprzecinkową, podczas gdy inne testy bezpośrednio manipulują. Muszę to rozgryźć. Może mieć coś wspólnego z płynnym interfejsem ShaderInput.
Również System.arrayCopy jest najwyraźniej nieco wolniejszy dla małych tablic niż zwykłe kopiowanie elementów z jednej tablicy do drugiej w pętli for. A użycie sun.misc.Unsafe (a także bezpośredniego java.nio.FloatBuffer, niepokazanego tutaj) wykonuje się niesamowicie.
źródło
Ostateczne (przynajmniej dla zmiennych i parametrów składowych) jest bardziej dla ludzi niż dla maszyny.
Dobrą praktyką jest, aby zmienne były ostateczne tam, gdzie to możliwe. Chciałbym, aby Java domyślnie uczyniła „zmiennymi” ostatecznymi i zawierała słowo kluczowe „Zmienne”, aby umożliwić zmiany. Niezmienne klasy prowadzą do znacznie lepszego kodu wątkowego, a samo spojrzenie na klasę z „końcowym” przed każdym elementem szybko pokaże, że jest niezmienna.
Kolejny przypadek - konwertowałem dużo kodu, aby używać adnotacji @ NonNull / @ Nullable (możesz powiedzieć, że parametr metody nie może mieć wartości null, wówczas IDE może ostrzec Cię o każdym miejscu, w którym przekazujesz zmienną, która nie jest oznaczona @ NonNull - cała sprawa rozprzestrzenia się w absurdalnym stopniu). O wiele łatwiej jest udowodnić, że zmienna członkowska lub parametr nie może mieć wartości zerowej, gdy jest oznaczony jako końcowy, ponieważ wiesz, że nie jest on ponownie przypisywany nigdzie indziej.
Moją sugestią jest, aby przyzwyczaić się do domyślnego stosowania finału dla członków i parametrów. To tylko kilka znaków, ale skłoni cię do poprawy stylu kodowania, jeśli nic więcej.
Finał dla metod lub klas to kolejna koncepcja, ponieważ nie pozwala na bardzo ważną formę ponownego użycia i tak naprawdę niewiele mówi czytelnikowi. Najlepszym zastosowaniem jest prawdopodobnie sposób, w jaki stworzyli String i inne wewnętrzne typy, abyś mógł polegać na konsekwentnym zachowaniu wszędzie - To zapobiegło wielu błędom (chociaż są chwile, gdybym LOVED wydłużył string ... och, możliwości)
źródło
Jak wspomniano w innym miejscu, „ostateczny” dla zmiennej lokalnej, aw nieco mniejszym stopniu zmiennej składowej, jest bardziej kwestią stylu.
„final” to stwierdzenie, że zmienna ma się nie zmieniać (tzn. zmienna się nie zmienia!). Kompilator może ci pomóc, składając skargę, jeśli naruszysz własne ograniczenia.
Podzielam pogląd, że Java byłby lepszym językiem, gdyby identyfikatory (przepraszam, po prostu nie mogę nazwać niczego niezmiennego „zmienną”) były domyślnie ostateczne i wymagałem, abyście wyraźnie powiedzieli, że są zmiennymi. Ale powiedziawszy to, ogólnie nie używam „końcowego” dla zmiennych lokalnych, które są inicjowane i nigdy nie są przypisywane; wydaje się po prostu zbyt głośno.
(Używam końcowego dla zmiennych składowych)
źródło
Członkowie zadeklarowani jako finałowi będą dostępni w całym programie, ponieważ w przeciwieństwie do nieoficjalnych, jeśli ci członkowie nie byli używani w programie, nadal nie byliby nimi objęci Garbage Collector, więc może to powodować problemy z wydajnością z powodu złego zarządzania pamięcią.
źródło
final
słowa kluczowego można używać na pięć sposobów w Javie.Klasa jest ostateczna: klasa jest ostateczna, co oznacza, że nie możemy zostać przedłużeni lub dziedziczenie oznacza, że dziedziczenie nie jest możliwe.
Podobnie - obiekt jest ostateczny: kiedyś nie zmodyfikowaliśmy wewnętrznego stanu obiektu, więc w takim przypadku możemy określić, że obiekt jest ostateczny. Przedmiot końcowy oznacza, że zmienna również nie jest ostateczna.
Gdy zmienna referencyjna jest ostateczna, nie można jej ponownie przypisać do innego obiektu. Ale może zmienić zawartość obiektu, o ile jego pola nie są ostateczne
źródło
final
znaczy, ale czy użyciefinal
wpływa na wydajność.