Czy w Javie powinienem używać „ostatecznego” parametrów i miejscowych, nawet jeśli nie muszę?

105

Java pozwala oznaczać zmienne (pola / locals / parametry) jako final, aby zapobiec ich ponownemu przypisaniu. Uważam, że jest to bardzo przydatne w przypadku pól, ponieważ pomaga mi szybko sprawdzić, czy niektóre atrybuty - lub cała klasa - mają być niezmienne.

Z drugiej strony uważam, że jest o wiele mniej przydatny w przypadku miejscowych i parametrów, i zwykle unikam oznaczania ich tak, finaljakby nawet nigdy nie zostaną ponownie przypisani (z oczywistym wyjątkiem, gdy trzeba ich użyć w klasie wewnętrznej) . Ostatnio jednak natknąłem się na kod, który używał finału, kiedy tylko jest to możliwe, co, jak sądzę, technicznie dostarcza więcej informacji.

Nie jestem już pewien swojego stylu programowania, zastanawiam się, jakie są inne zalety i wady stosowania w finaldowolnym miejscu, jaki jest najbardziej popularny styl branżowy i dlaczego.

Dąb
źródło
Pytania w stylu kodowania @Amir wydają się pasować tutaj lepiej niż na SO, i nie mogłem znaleźć żadnych zasad w FAQ ani w meta tej stronie dotyczących tego. Czy możesz proszę mnie skierować?
Dąb
1
@Oak Mówi: „Konkretny problem z programowaniem, algorytmy oprogramowania, kodowanie , pytaj o przepełnienie stosu”
Amir Rezaei
7
@Amir Nie zgadzam się, nie rozumiem, jak to jest problem z kodowaniem. W każdym razie ta debata nie należy tutaj, więc otworzyłem meta-temat na ten temat .
Dąb
3
@amir jest to całkowicie temat na tej stronie, ponieważ jest to subiektywne pytanie dotyczące programowania - zobacz / faq
Jeff Atwood
1
Re, zmienne lokalne: starsze kompilatory Java zwracały uwagę na to, czy zadeklarowałeś lokalny finalczy nie, i odpowiednio je zoptymalizowałeś. Nowoczesne kompilatory są na tyle inteligentne, aby sami to wymyślić. Przynajmniej w przypadku zmiennych lokalnych, finaljest to wyłącznie z korzyścią dla ludzkich czytelników. Jeśli twoje procedury nie są zbyt skomplikowane, to większość ludzkich czytelników powinna być w stanie to sobie wyobrazić.
Solomon Slow

Odpowiedzi:

67

Używam finaltego samego co ty. Dla mnie wygląda to na zbędne w stosunku do lokalnych zmiennych i parametrów metody i nie przekazuje użytecznych dodatkowych informacji.

Jedną ważną rzeczą jest dążenie do tego, aby moje metody były krótkie i czyste , z których każda wykonuje jedno zadanie. Zatem moje lokalne zmienne i parametry mają bardzo ograniczony zakres i są używane tylko do jednego celu. Minimalizuje to szanse na przypadkowe przypisanie ich.

Co więcej, jak zapewne wiesz, finalnie gwarantuje, że nie możesz zmienić wartości / stanu (nieprymitywnej) zmiennej. Tyle tylko, że po zainicjowaniu nie można ponownie przypisać odwołania do tego obiektu. Innymi słowy, działa płynnie tylko ze zmiennymi typów pierwotnych lub niezmiennych. Rozważać

final String s = "forever";
final int i = 1;
final Map<String, Integer> m = new HashMap<String, Integer>();

s = "never"; // compilation error!
i++; // compilation error!
m.put(s, i); // fine

Oznacza to, że w wielu przypadkach nadal nie ułatwia to zrozumienia, co dzieje się w kodzie, a nieporozumienie może w rzeczywistości powodować subtelne błędy, które są trudne do wykrycia.

Péter Török
źródło
1
Jeśli chodzi o edycję - jestem świadomy semantyki final, dziękuję :), ale dobra uwaga na temat metod krótkich i czystych - myślę, że jeśli metoda jest na tyle krótka, że ​​oczywiste jest, że zmienna nie jest ponownie przypisywana, istnieje nawet mniej motywu do rozważenia finalsłowa kluczowego.
Dąb
Czy nie byłoby wspaniale, gdybyśmy mieli końcowe parametry i zmienne lokalne, a także krótką i czystą składnię? programmers.stackexchange.com/questions/199783/…
oberlies
7
„final nie gwarantuje, że nie można zmienić wartości / stanu zmiennej (nieprymitywnej). Tyle tylko, że nie można ponownie przypisać odwołania do tego obiektu po zainicjowaniu.” Nonprimitive = referencja (jedynymi typami w Javie są pierwotne typy i typy referencji). Wartość zmiennej typu odwołania jest odwołaniem. Dlatego nie można ponownie przypisać odwołania = nie można zmienić wartości.
user102008,
2
+1 za pułapkę odniesienia / stanu. Należy jednak pamiętać, że oznaczenie zmiennych końcowych może być konieczne do utworzenia zamknięć w nowych aspektach funkcjonalnych
Java8
Twoje porównanie jest tendencyjne, jak wskazał @ user102008. Przypisanie zmiennej nie jest tym samym co aktualizacja wartości
er.
64

Twój styl programowania Java i myśli są w porządku - nie musisz w to wątpić.

Z drugiej strony uważam, że jest o wiele mniej przydatny w przypadku miejscowych i parametrów, i zwykle unikam oznaczania ich jako ostatecznych, nawet jeśli nigdy nie zostaną ponownie przypisani (z oczywistym wyjątkiem, gdy trzeba ich użyć w klasie wewnętrznej ).

Właśnie dlatego powinieneś użyć finalsłowa kluczowego. Ci twierdzą, że TY wiesz, że nigdy nie być ponownie przydzielony, ale nikt nie wie. finalNatychmiastowe użycie ujednoznacznia twój kod.

Jonathan Khoo
źródło
8
Jeśli twoja metoda jest jasna i robi tylko jedną rzecz, czytelnik też będzie wiedział. Ostatnie słowo powoduje, że kod jest nieczytelny. A jeśli jest nieczytelny, jest o wiele bardziej niejednoznaczny
eddieferetro
4
@eddieferetro Disagreed. Słowo kluczowe finalokreśla zamiar, dzięki czemu kod jest bardziej czytelny. Ponadto, gdy będziesz musiał radzić sobie z kodem z prawdziwego świata, zauważysz, że rzadko jest on nieskazitelny i przejrzysty, a swobodne dodawanie finalwszędzie tam, gdzie możesz, pomoże Ci wykryć błędy i lepiej zrozumieć starszy kod.
Andres F.
4
Czy „intencją” jest to, że zmienna nigdy się nie zmieni i że Twój kod opiera się na tym fakcie ? Czy też intencją jest „tak się po prostu dzieje, że ta zmienna nigdy się nie zmienia, więc zaznaczam ją jako ostateczną”. Później jest bezużyteczny i prawdopodobnie szkodliwy hałas. A ponieważ wydaje się, że jesteś zwolennikiem oznaczania wszystkich mieszkańców, robisz to później. Duży -1.
user949300,
2
finalstwierdza zamiar, co zwykle jest dobrą rzeczą w kodzie, ale wiąże się to z kosztem dodania wizualnego bałaganu. Podobnie jak większość rzeczy w programowaniu, istnieje tutaj kompromis: czy fakt, że twoja zmienna będzie używana tylko raz, będzie wart dodatkowej gadatliwości?
christopheml,
30

Jedną z zalet używania final/ constwszędzie tam, gdzie to możliwe, jest to, że zmniejsza obciążenie umysłowe czytnika twojego kodu.

Można go zapewnić, że wartość / odniesienie nigdy się nie zmieni. Nie musi więc zwracać uwagi na modyfikacje, aby zrozumieć obliczenia.

Zmieniłem zdanie na ten temat po nauce czysto funkcjonalnych języków programowania. Chłopcze, co za ulga, jeśli możesz zaufać „zmiennej”, która zawsze zachowa swoją wartość początkową.

LennyProgrammers
źródło
16
Cóż, w Javie finalnie gwarantuje się, że nie można zmienić wartości / stanu (niepodatkowej) zmiennej. Tyle tylko, że po zainicjowaniu nie można ponownie przypisać odwołania do tego obiektu.
Péter Török
9
Wiem, dlatego odróżniłem wartość od odniesienia. Ta koncepcja jest najbardziej użyteczna w kontekście niezmiennych struktur danych i / lub czystej funkcjonalności.
LennyProgrammers
7
@ PéterTörök Jest natychmiast pomocny w przypadku typów pierwotnych i nieco pomocny w odniesieniu do zmiennych obiektów (przynajmniej wiesz, że zawsze masz do czynienia z tym samym obiektem!). Jest to niezwykle pomocne, gdy mamy do czynienia z kodem zaprojektowanym tak, aby był niezmienny od podstaw.
Andres F.
18

finalW parametrach metody i zmiennych lokalnych uważam szum za kod. Deklaracje metod Java mogą być dość długie (szczególnie w przypadku generyków) - nie trzeba ich już robić.

Jeśli testy jednostkowe są napisane poprawnie, przypisując do parametrów, które jest „szkodliwe” będzie podniósł, więc nigdy nie powinny faktycznie być problemem. Wizualna przejrzystość jest ważniejsza niż unikanie możliwego błędu, który nie został wykryty, ponieważ twoje testy jednostkowe mają niewystarczający zasięg.

Narzędzia takie jak FindBugs i CheckStyle, które można skonfigurować tak, aby przerywały kompilację, jeśli przypisano parametry lub zmienne lokalne, jeśli bardzo zależy ci na takich rzeczach.

Oczywiście, jeśli trzeba , aby je ostateczna, na przykład dlatego, że używasz wartości w anonimowej klasy, wtedy nie ma problemu - to najprostsza najczystsze rozwiązanie.

Oprócz oczywistego efektu dodania dodatkowych słów kluczowych do parametrów, a tym samym kamuflowania ich przez IMHO, dodanie finału do parametrów metody może często spowodować, że kod w treści metody stanie się mniej czytelny, co pogorszy kod - aby był „dobry”, kod musi być tak czytelny i jak najprostszy. Dla wymyślonego przykładu, powiedzmy, że mam metodę, która musi rozróżniać przypadki bez rozróżnienia.

Bez final:

public void doSomething(String input) {
    input = input.toLowerCase();
    // do a few things with input
}

Prosty. Czysty. Wszyscy wiedzą, co się dzieje.

Teraz z „ostateczną” opcją 1:

public void doSomething(final String input) {
    final String lowercaseInput = input.toLowerCase();
    // do a few things with lowercaseInput
}

Podczas gdy parametry powodują, że finalkoder nie dodaje kodu dalej, niż myśli, że pracuje z pierwotną wartością, istnieje jednakowe ryzyko, że kod użyty niżej może użyć inputzamiast tego lowercaseInput, czego nie powinien i przed którym nie może się zabezpieczyć, ponieważ można „ t wyjąć z zakresu (lub nawet przypisać nulldo inputjeżeli byłoby nawet pomóc w każdym razie).

Z „ostatecznym” wariantem 2:

public void doSomething(final String input) {
    // do a few things with input.toLowerCase()
}

Teraz stworzyliśmy jeszcze więcej szumu kodu i wprowadziliśmy hit wydajności związany z koniecznością wywoływania toLowerCase()n razy.

Z „ostatecznym” wariantem 3:

public void doSomething(final String input) {
    doSomethingPrivate(input.toLowerCase());
}

/** @throws IllegalArgumentException if input not all lower case */
private void doSomethingPrivate(final String input) {
    if (!input.equals(input.toLowerCase())) {
        throw new IllegalArgumentException("input not lowercase");
    }
    // do a few things with input
}

Mów o szumie kodu. To jest wrak pociągu. Mamy nową metodę, wymagany blok wyjątków, ponieważ inny kod może wywołać ją niepoprawnie. Więcej testów jednostkowych w celu uwzględnienia wyjątku. Wszystko po to, by uniknąć jednej prostej, preferowanej i nieszkodliwej linii IMHO.

Istnieje również problem polegający na tym, że metody nie powinny być tak długie, aby nie można było łatwo ich wizualnie przyjąć i na pierwszy rzut oka wiedzieć, że miało miejsce przypisanie parametru.

Myślę, że dobrą praktyką / stylem jest to, że jeśli przypisujesz do parametru, robisz to na początku metody, najlepiej w pierwszym wierszu lub bezpośrednio po podstawowym sprawdzaniu danych wejściowych, skutecznie zastępując go dla całej metody, co ma spójny efekt w metoda. Czytelnicy wiedzą, że można oczekiwać, że każde zadanie będzie oczywiste (w pobliżu deklaracji podpisu) i w spójnym miejscu, co znacznie złagodzi problem, którego próbuje uniknąć dodania ostatecznego. Właściwie rzadko przypisuję parametry, ale jeśli to robię, zawsze robię to na początku metody.


Zauważ też, że finaltak naprawdę cię nie chroni, jak mogłoby się początkowo wydawać:

public void foo(final Date date) {
    date.setTime(0); 
    // code that uses date
}

final nie chroni cię całkowicie, chyba że typ parametru jest prymitywny lub niezmienny.

Czech
źródło
W ostatnim przypadku finalzapewnia częściową gwarancję, że masz do czynienia z tą samą Dateinstancją. Niektóre gwarancje są lepsze niż nic (jeśli już, to od samego początku opowiadasz się za niezmiennymi klasami!). W każdym razie w praktyce wiele z tego, co mówisz, powinno wpływać na istniejące niezmienne domyślnie języki, ale tak nie jest, co oznacza, że ​​to nie jest problem.
Andres F.,
+1 za wskazanie na ryzyko, że „dalszy kod może wykorzystywać dane wejściowe”. Mimo to wolałbym, aby moje parametry były takie final, z możliwością uczynienia ich niekończącymi się dla przypadków takich jak powyżej. Po prostu nie warto spamować podpisu słowem kluczowym.
maaartinus
7

Pozwalam zaćmieniu umieścić finalprzed każdą zmienną lokalną, ponieważ uważam, że ułatwia to czytanie programu. Nie robię tego z parametrami, ponieważ chcę, aby lista parametrów była jak najkrótsza, idealnie powinna pasować do jednej linii.

maaartinus
źródło
2
Ponadto w przypadku parametrów może pojawić się ostrzeżenie lub błąd kompilatora, jeśli parametr jest przypisany.
oberlies
3
Wręcz przeciwnie, jest mi trudniej czytać, ponieważ po prostu zaśmieca kod.
Steve Kuo,
3
@ Steve Kuo: To pozwala mi tylko szybko znaleźć wszystkie zmienne. nie ma dużego zysku, ale wraz z zapobieganiem przypadkowym zadaniom jest wart 6 znaków. Byłbym o wiele bardziej szczęśliwy, gdyby istniało coś takiego jak varoznaczanie zmiennych nie-końcowych. YMMV.
maaartinus,
1
@maaartinus Uzgodniono var! Niestety w Javie nie jest to ustawienie domyślne, a teraz jest już za późno na zmianę języka. Dlatego jestem gotów znieść drobną niedogodność związaną z pisaniem final:)
Andres F.,
1
@maaartinus Zgoda. Uważam, że około 80–90% moich (lokalnych) zmiennych nie wymaga modyfikacji po inicjalizacji. Dlatego 80-90% z nich ma finalsłowo kluczowe wcześniej, a tylko 10-20% potrzebuje var...
JimmyB 24.10.16