Kiedy należy używać final dla parametrów metody i zmiennych lokalnych?


171

Znalazłem kilka odniesień ( na przykład ), które sugerują używanie finaljak największej ilości i zastanawiam się, jakie to ważne. Dzieje się tak głównie w kontekście parametrów metod i zmiennych lokalnych, a nie ostatecznych metod lub klas. W przypadku stałych ma to oczywisty sens.

Z jednej strony kompilator może dokonać pewnych optymalizacji, a to sprawia, że ​​intencje programisty są jaśniejsze. Z drugiej strony dodaje szczegółowości, a optymalizacje mogą być trywialne.

Czy jest to coś, o czym powinienem starać się pamiętać?


Oto powiązany post do przejrzenia: stackoverflow.com/questions/137868/ ...
mattlant


1
Głosuję za głosem tylko dlatego, że nie wiedziałem, że przed przeczytaniem tego można użyć final jako modyfikatora parametrów. Dzięki!
Kip

Odpowiedzi:


178

Obsesja na punkcie:

  • Pola końcowe - oznaczenie pól jako ostatecznych wymusza ich ustawienie do końca konstrukcji, dzięki czemu odniesienie do pola jest niezmienne. Pozwala to na bezpieczną publikację pól i pozwala uniknąć konieczności synchronizacji przy późniejszych odczytach. (Zauważ, że w przypadku odwołania do obiektu tylko odwołanie do pola jest niezmienne - rzeczy, do których odwołuje się odwołanie do obiektu, mogą się zmieniać, a to wpływa na niezmienność.)
  • Końcowe pola statyczne - chociaż używam teraz wyliczeń w wielu przypadkach, w których używałem statycznych pól końcowych.

Rozważ, ale używaj rozważnie:

  • Klasy końcowe - projekt Framework / API to jedyny przypadek, w którym o tym myślę.
  • Metody końcowe - Zasadniczo takie same jak zajęcia końcowe. Jeśli używasz wzorców metod szablonowych, takich jak szalone i oznaczanie rzeczy jako ostateczne, prawdopodobnie zbytnio polegasz na dziedziczeniu, a za mało na delegowaniu.

Ignoruj, chyba że czujesz anal:

  • Parametry metod i zmienne lokalne - RZADKO to robię głównie dlatego, że jestem leniwy i zaśmieca to kod. W pełni przyznam, że oznaczanie parametrów i zmiennych lokalnych, których nie zamierzam modyfikować, jest „trafniejsze”. Chciałbym, żeby to było domyślne. Ale tak nie jest i uważam, że kod jest trudniejszy do zrozumienia, ponieważ finały są wszędzie. Jeśli jestem w czyimś kodzie, nie zamierzam ich wyciągać, ale jeśli piszę nowy kod, nie umieszczam ich. Jedynym wyjątkiem jest sytuacja, w której musisz oznaczyć coś ostatecznego, aby mieć dostęp to z anonimowej klasy wewnętrznej.

  • Edycja: zwróć uwagę, że jeden przypadek użycia, w którym końcowe zmienne lokalne są w rzeczywistości bardzo przydatne, jak wspomniał @ adam-gent, to przypadek, gdy wartość zostanie przypisana zmiennej w gałęzi if/ else.


8
Joshua Bloch argumentuje, że wszystkie klasy powinny być zdefiniowane jako ostateczne, chyba że są przeznaczone do dziedziczenia. Zgadzam się z nim; Dodam final do każdej klasy, która implementuje interfejs (aby móc tworzyć testy jednostkowe). Oznacz również jako ostateczne wszystkie chronione / klasowe metody, które nie zostaną nadpisane.
rmaruszewski

61
Z całym szacunkiem dla Josha Blocha (i to jest znaczna kwota), nie zgadzam się z ogólnym przypadkiem. W przypadku tworzenia API, pamiętaj o zablokowaniu rzeczy. Budowanie własnego kodu, wznoszenie murów, które później będziesz musiał burzyć, to strata czasu.
Alex Miller

9
Zdecydowanie nie jest to „strata czasu”, zwłaszcza, że ​​nie kosztuje to wcale czasu… W aplikacji zwykle finaldomyślnie robię prawie wszystkie klasy . Możesz nie zauważyć korzyści, jeśli nie używasz prawdziwie nowoczesnego środowiska Java IDE (tj. IDEA).
Rogério

9
IDEA ma (po wyjęciu z pudełka) setki inspekcji kodu, a niektóre z nich mogą wykryć nieużywany / niepotrzebny kod w finalklasach / metodach. Na przykład, jeśli ostatnia metoda zadeklaruje zgłoszenie sprawdzonego wyjątku, ale nigdy go nie zgłasza, IDEA powie ci to i możesz usunąć wyjątek z throwsklauzuli. Czasami można również znaleźć nieużywane całe metody, które można wykryć, gdy nie można ich zastąpić.
Rogério

8
@rmaruszewski Oznaczanie klas jako ostatecznych uniemożliwia większości fałszywych frameworków możliwość ich kpienia, co utrudnia testowanie kodu. Zrobiłbym klasę finałową tylko wtedy, gdyby było niezwykle ważne, aby jej nie przedłużać.
Mike Rylander,

44

Czy jest to coś, o czym powinienem starać się pamiętać?

Nie, jeśli używasz Eclipse, ponieważ możesz skonfigurować akcję zapisywania, aby automatycznie dodać te końcowe modyfikatory. Wtedy zyskujesz mniejszym wysiłkiem.


3
Świetna wskazówka dotycząca akcji zapisywania, nie wiedziałem o tym.
zdrowie psychiczne

1
Zastanawiam się głównie nad korzyścią, że final sprawia, że ​​kod jest bezpieczniejszy przed błędami, przez przypadkowe przypisanie do niewłaściwej zmiennej, zamiast wszelkich optymalizacji, które mogą się wydarzyć lub nie.
Peter Hilton,

4
Czy to naprawdę problem dla Ciebie? Jak często miałeś / aś błąd, który z tego wyniknął?
Alex Miller,

1
+1 za przydatną wskazówkę w Eclipse. Myślę, że powinniśmy używać wersji Final tak często, jak to możliwe, aby uniknąć błędów.
emeraldhieu

Czy możesz to zrobić również w IntelliJ?
Koray Tugay

15

Korzyści wynikające z „ostatecznej wersji” w czasie opracowywania są co najmniej tak samo istotne, jak korzyści w czasie wykonywania. Informuje przyszłych redaktorów kodu o Twoich zamiarach.

Oznaczenie klasy jako „ostateczna” wskazuje, że podczas projektowania lub implementacji klasy nie podjęto żadnych wysiłków, aby z wdziękiem obsługiwać rozszerzenie. Jeśli czytelnicy mogą dokonać zmian w klasie i chcą usunąć „końcowy” modyfikator, mogą to zrobić na własne ryzyko. Do nich należy upewnienie się, że klasa będzie dobrze obsługiwać rozszerzenie.

Oznaczanie zmiennej jako „final” (i przypisywanie jej w konstruktorze) jest przydatne przy wstrzykiwaniu zależności. Wskazuje na „współpracujący” charakter zmiennej.

Oznaczanie metody jako „ostateczna” jest przydatne w klasach abstrakcyjnych. Wyraźnie określa, gdzie znajdują się punkty przedłużenia.


11

finalCały czas staram się, aby Java była bardziej oparta na wyrażeniach. Zobacz warunki Java (if,else,switch ) nie są oparte na wyrażeniach, których zawsze nienawidziłem, zwłaszcza jeśli jesteś przyzwyczajony do programowania funkcjonalnego (np. ML, Scala lub Lisp).

Dlatego powinieneś zawsze (IMHO) używać zmiennych końcowych podczas używania warunków.

Dam ci przykład:

    final String name;
    switch(pluginType) {
        case CANDIDATE_EXPORT:
            name = "Candidate Stuff";
            break;
        case JOB_POSTING_IMPORT:
            name = "Blah";
            break;
        default:
            throw new IllegalStateException();
    }

Teraz Jeśli dodasz kolejną caseinstrukcję i nie ustawisz namekompilatora, nie powiedzie się. Kompilator również zawiedzie, jeśli nie przerwiesz w każdym przypadku (ustawisz zmienną). Dzięki temu Java jest bardzo podobna do Lispalet wyrażeń i sprawia, że ​​kod nie jest masowo wcięty (z powodu leksykalnych zmiennych zakresu).

I jak zauważył @Recurse (ale najwyraźniej -1 mnie) możesz zrobić to, co było wcześniej, bez konieczności wykonywania String name finalbłędu kompilatora (którego nigdy nie powiedziałem, że nie możesz), ale możesz łatwo usunąć błąd kompilatora ustawiając nazwę po przełączeniu instrukcja odrzucająca semantykę wyrażenia lub gorzej zapominająca, do breakktórej nie można spowodować błędu (pomimo tego, co mówi @Recurse) bez użycia final:

    String name;
    switch(pluginType) {
        case CANDIDATE_EXPORT:
            name = "Candidate Stuff";
            //break; whoops forgot break.. 
            //this will cause a compile error for final ;P @Recurse
        case JOB_POSTING_IMPORT:
            name = "Blah";
            break;
    }
    // code, code, code
    // Below is not possible with final
    name = "Whoops bug";

Ze względu na nazwę ustawienia błędu (oprócz zapomnienia, do breakktórego również innego błędu) mogę teraz przypadkowo zrobić to:

    String name;
    switch(pluginType) {
        case CANDIDATE_EXPORT:
            name = "Candidate Stuff";
            break;
        //should have handled all the cases for pluginType
    }
    // code, code, code
    // Below is not possible with final
    name = "Whoops bug";

Ostatnia zmienna wymusza pojedynczą ocenę tego, jaka powinna być nazwa. Podobnie jak funkcja, która ma zwracaną wartość, musi zawsze zwracać wartość (ignorując wyjątki), blok przełącznika nazwy będzie musiał rozwiązać nazwę, a tym samym powiązany z tym blokiem przełącznika, co ułatwia refaktoryzację fragmentów kodu (np. Metoda Eclipe: extract) .

Powyższe w OCaml:

type plugin = CandidateExport | JobPostingImport

let p = CandidateExport

let name = match p with
    | CandidateExport -> "Candidate Stuff"
    | JobPostingImport -> "Blah" ;;

Do match ... with ...ocenia jak funkcja tj wypowiedzi. Zwróć uwagę, jak wygląda nasza instrukcja switch.

Oto przykład w schemacie (rakieta lub kurczak):

(define name 
    (match b
      ['CandidateExport "Candidate Stuff"]
      ['JobPostingImport "Blah"]))

1
Tyle tylko, że kompilator java już wyświetli błąd „nazwa potencjalnie niezainicjowana” i odmówi kompilacji z wersją finalną lub bez niej.
Powtórz

4
Tak, ale bez finału możesz go zresetować w dowolnym momencie. Rzeczywiście widziałem, jak to się dzieje, gdy programista zamienia zmienną an else if (...)w a, if(...)a tym samym resetuje zmienną. Pokazałem mu, że nigdy by się to nie zdarzyło przy ostatecznej zmiennej. Zasadniczo finalzmusza cię do przypisania zmiennej raz i tylko raz ... więc: P
Adam Gent,

9

Znalazłem oznaczanie parametrów metod i lokalizacji, które finalsą przydatne jako pomoc w refaktoryzacji, gdy dana metoda jest niezrozumiałym bałaganem o długości kilku stron. Posypaćfinal swobodnie, zobacz, jakie błędy „nie można przypisać do zmiennej końcowej”, które zgłasza kompilator (lub twoje IDE), i możesz po prostu odkryć, dlaczego zmienna o nazwie „data” kończy się wartością zerową, mimo że kilka (nieaktualnych) komentarzy przysięga, że ​​może nie zdarzyło się.

Następnie możesz naprawić niektóre błędy, zastępując ponownie używane zmienne nowymi zmiennymi zadeklarowanymi bliżej punktu użycia. Wtedy odkrywasz, że możesz zawinąć całe części metody w nawiasy ustalające zakres i nagle jesteś o jedno naciśnięcie klawisza IDE od "metody wyodrębniania", a twój potwór stał się bardziej zrozumiały.

Jeśli twoja metoda nie jest już nie do naprawienia wrakiem, myślę, że może być wartościowe uczynienie rzeczy ostatecznych, aby zniechęcić ludzi do przekształcenia ich we wspomniany wrak; ale jeśli jest to krótka metoda (patrz: nie nie do utrzymania), ryzykujesz dodanie dużej ilości szczegółowości. W szczególności podpisy funkcji Java są wystarczająco trudne, aby zmieścić się do 80 znaków bez dodawania kolejnych sześciu na argument!


1
Bardzo ważny ostatni punkt, chociaż już dawno zrezygnowałem z limitu 80 znaków, ponieważ rozdzielczość ekranu nieco się zmieniła w ciągu ostatnich 10 lat. Mogę łatwo zmieścić 300 znaków na ekranie bez przewijania. Niemniej jednak czytelność jest oczywiście lepsza bez finalprzed każdym parametrem.
brimborium

8

Cóż, to wszystko zależy od twojego stylu ... jeśli LUBISZ oglądać finał, kiedy nie będziesz modyfikować zmiennej, użyj jej. Jeśli nie lubisz tego oglądać ... zostaw to.

Osobiście lubię jak najmniej szczegółowości, więc staram się unikać dodatkowych słów kluczowych, które nie są naprawdę potrzebne.

Wolę jednak języki dynamiczne, więc prawdopodobnie nie jest zaskoczeniem, że lubię unikać gadatliwości.

Więc powiedziałbym, że po prostu wybierz kierunek, w którym się skręcasz i po prostu idź za nim (niezależnie od przypadku, staraj się być konsekwentny).


Na marginesie, pracowałem nad projektami, które zarówno używają, jak i nie używają takiego wzorca, i nie widziałem różnicy w ilości błędów lub błędów ... Nie sądzę, że jest to wzór, który będzie ogromnie popraw liczbę błędów lub cokolwiek innego, ale znowu to jest styl, a jeśli lubisz wyrażać zamiar, że nie będziesz go modyfikować, to śmiało go używaj.


6

W parametrach przydatne jest uniknięcie przypadkowej zmiany wartości parametru i wprowadzenie subtelnego błędu. Zwykłem ignorować to zalecenie, ale po spędzeniu około 4 godzin. w okropną metodę (z setkami linii kodu i wieloma forami, zagnieżdżonymi ifs i wszelkiego rodzaju złymi praktykami) radziłbym ci to zrobić.

 public int processSomethingCritical( final int x, final int y ){
 // hundreds of lines here 
     // for loop here...
         int x2 = 0;
        x++; // bug aarrgg...
 // hundreds of lines there
 // if( x == 0 ) { ...

 }

Oczywiście w idealnym świecie to by się nie wydarzyło, ale… cóż… czasami trzeba wspierać kod innych. :(


2
Ta metoda ma poważniejsze problemy niż brakujący finał. To dość rzadkie, choć nie niemożliwe, że istnieje dobry powód, dla którego metoda jest tak zagracona, że ​​mogą wystąpić tego rodzaju błędy. Odrobina myśli umieszczona w nazwach zmiennych znacznie przyczyniłaby się do takich wypadków.
ykaganovich

2
Jeśli masz „setki linii kodu” w jednej metodzie, możesz podzielić ją na kilka mniejszych metod.
Steve Kuo

5

Jeśli piszesz aplikację, że ktoś będzie musiał czytać kod po powiedzmy 1 roku, to tak, użyj final na zmiennej, która nie powinna być ciągle modyfikowana. Robiąc to, twój kod będzie bardziej „samodokumentujący się”, a także zmniejszysz szansę innych programistów na robienie głupich rzeczy, takich jak używanie lokalnej stałej jako lokalnej zmiennej tymczasowej.

Jeśli piszesz jakiś jednorazowy kod, nie trudź się identyfikowaniem wszystkich stałych i sprawiaj, że będą ostateczne.


4

Będę korzystał z finału, jak tylko będę mógł. Takie postępowanie spowoduje oznaczenie, jeśli przypadkowo zmienisz pole. Ustawiłem również parametry metody na końcowe. Robiąc to, złapałem kilka błędów w kodzie, które przejąłem, kiedy próbują „ustawić” parametr, zapominając, że Java przechodzi przez wartość.


2

Nie jest jasne, czy jest to oczywiste, ale ostateczne ustawienie parametru metody ma wpływ tylko na jej treść. Robi NIE przekazać jakieś ciekawe informacje na temat zamiarów metody do wywołującego. Przekazywany obiekt nadal może być mutowany w metodzie (finały nie są stałymi), a zakres zmiennej mieści się w metodzie.

Odpowiadając na Twoje dokładne pytanie, nie zawracałbym sobie głowy nadaniem ostatecznej wartości instancji lub zmiennej lokalnej (w tym parametrom metody), chyba że kod tego wymagał (np. Odwołanie do zmiennej pochodzi z klasy wewnętrznej) lub wyjaśnieniem jakiejś naprawdę skomplikowanej logiki.

Na przykład zmienne, uczyniłbym je ostatecznymi, gdyby były logicznymi stałymi.


2

Zmienna ma wiele zastosowań final. Oto tylko kilka

Ostateczne stałe

 public static class CircleToolsBetter {
     public final static double PI = 3.141;
        public double getCircleArea(final double radius) {
          return (Math.pow(radius, 2) * PI);
        }
    }

Można to następnie wykorzystać do innych części kodów lub uzyskać do nich dostęp inne klasy, w ten sposób, gdybyś kiedykolwiek zmienił wartość, nie musiałbyś zmieniać ich jeden po drugim.

Zmienne końcowe

public static String someMethod(final String environmentKey) {
    final String key = "env." + environmentKey;
    System.out.println("Key is: " + key);
    return (System.getProperty(key));

  }

}

W tej klasie tworzysz zmienną końcową o określonym zakresie, która dodaje prefiks do parametru environmentKey. W tym przypadku zmienna końcowa jest ostateczna tylko w zakresie wykonania, który jest inny przy każdym wykonaniu metody. Za każdym razem, gdy wprowadzana jest metoda, finał jest odtwarzany. Tak szybko, jak jest skonstruowana, nie można jej zmienić w zakresie wykonywania metody. Pozwala to naprawić zmienną w metodzie na czas jej trwania. patrz poniżej:

public class FinalVariables {


  public final static void main(final String[] args) {
    System.out.println("Note how the key variable is changed.");
    someMethod("JAVA_HOME");
    someMethod("ANT_HOME");
  }
}

Ostateczne stałe

public double equation2Better(final double inputValue) {
    final double K = 1.414;
    final double X = 45.0;

double result = (((Math.pow(inputValue, 3.0d) * K) + X) * M);
double powInputValue = 0;         
if (result > 360) {
  powInputValue = X * Math.sin(result); 
} else {
  inputValue = K * Math.sin(result);   // <= Compiler error   
}

Są one szczególnie przydatne, gdy masz naprawdę długie wiersze kodów i generują błąd kompilatora, więc nie napotkasz błędu logicznego / biznesowego, gdy ktoś przypadkowo zmieni zmienne, których nie należy zmieniać.

Ostateczne kolekcje

Inny przypadek, gdy mówimy o kolekcjach, musisz ustawić je jako niemodyfikowalne.

 public final static Set VALID_COLORS; 
    static {
      Set temp = new HashSet( );
      temp.add(Color.red);
      temp.add(Color.orange);
      temp.add(Color.yellow);
      temp.add(Color.green);
      temp.add(Color.blue);
      temp.add(Color.decode("#4B0082")); // indigo
      temp.add(Color.decode("#8A2BE2")); // violet
      VALID_COLORS = Collections.unmodifiableSet(temp);
    }

w przeciwnym razie, jeśli nie ustawisz go jako niemodyfikowalnego:

Set colors = Rainbow.VALID_COLORS;
colors.add(Color.black); // <= logic error but allowed by compiler

Klasy końcowe i metody końcowe nie mogą być odpowiednio przedłużane ani nadpisywane.

EDYCJA: ABY ROZWIĄZAĆ KOŃCOWY PROBLEM KLASOWY DOTYCZĄCY ENCAPSULACJI:

Istnieją dwa sposoby, aby zakończyć klasę. Pierwszym jest użycie słowa kluczowego final w deklaracji klasy:

public final class SomeClass {
  //  . . . Class contents
}

Drugim sposobem nadania klasy ostatecznej jest zadeklarowanie wszystkich jej konstruktorów jako prywatnych:

public class SomeClass {
  public final static SOME_INSTANCE = new SomeClass(5);
  private SomeClass(final int value) {
  }

Oznaczanie go jako ostatecznego oszczędza kłopotów, jeśli dowiesz się, że jest to ostateczny, aby zademonstrować spojrzenie na tę klasę testu. wygląda publicznie na pierwszy rzut oka.

public class Test{
  private Test(Class beanClass, Class stopClass, int flags)
    throws Exception{
    //  . . . snip . . . 
  }
}

Niestety, ponieważ jedyny konstruktor tej klasy jest prywatny, nie jest możliwe rozszerzenie tej klasy. W przypadku klasy Test nie ma powodu, aby zajęcia były ostateczne. Klasa Test jest dobrym przykładem tego, jak niejawne klasy końcowe mogą powodować problemy.

Dlatego powinieneś oznaczyć ją jako ostateczną, gdy niejawnie ustawiasz klasę jako ostateczną, ustawiając jej konstruktor jako prywatny.


1

Trochę kompromisu, jak wspomniałeś, ale wolę jawne użycie czegoś ponad niejawne użycie. Pomoże to usunąć pewne niejasności dla przyszłych opiekunów kodu - nawet jeśli to tylko Ty.


1

Jeśli masz klasy wewnętrzne (anonimowe), a metoda musi mieć dostęp do zmiennej metody zawierającej, musisz mieć tę zmienną jako ostateczną.

Poza tym to, co powiedziałeś, jest słuszne.


Teraz java 8 oferuje elastyczność dzięki efektywnym zmiennym końcowym.
Ravindra babu

0

Użyj finalsłowa kluczowego dla zmiennej, jeśli tworzysz tę zmienną jakoimmutable

Deklarując zmienną jako ostateczną, pomaga programistom wykluczyć ewentualne problemy z modyfikacją zmiennych w wysoce wielowątkowym środowisku.

W wydaniu Java 8 mamy jeszcze jedną koncepcję o nazwie „ effectively final variable”. Zmienna nieostateczna może być zmienną końcową.

zmienne lokalne, do których odwołuje się wyrażenie lambda, muszą być ostateczne lub faktycznie ostateczne

Pod uwagę brana jest zmienna obowiązującą jako ostateczną, jeśli nie została zmodyfikowana po inicjalizacji w bloku lokalnym. Oznacza to, że możesz teraz używać zmiennej lokalnej bez słowa kluczowego final wewnątrz anonimowej klasy lub wyrażenia lambda, pod warunkiem, że muszą one być faktycznie ostateczne.

Aż do Java 7 nie można używać niekońcowej zmiennej lokalnej wewnątrz anonimowej klasy, ale w Javie 8 jest to możliwe

Spójrz na ten artykuł


-1

Po pierwsze, ostatnie słowo kluczowe służy do uczynienia zmiennej stałą. Stała oznacza, że ​​się nie zmienia. Na przykład:

final int CM_PER_INCH = 2.54;

Zadeklarowałbyś zmienną jako ostateczną, ponieważ centymetr na cal się nie zmienia.

Jeśli spróbujesz zastąpić ostateczną wartość, zmienna jest tym, co została zadeklarowana jako pierwsza. Na przykład:

final String helloworld = "Hello World";
helloworld = "A String"; //helloworld still equals "Hello World"

Wystąpił błąd kompilacji, który wygląda mniej więcej tak:

local variable is accessed from inner class, must be declared final

Jeśli twoja zmienna nie może być zadeklarowana jako ostateczna lub jeśli nie chcesz deklarować jej jako ostatecznej, spróbuj tego:

final String[] helloworld = new String[1];
helloworld[0] = "Hello World!";
System.out.println(helloworld[0]);
helloworld[0] = "A String";
System.out.println(helloworld[0]);

Spowoduje to wydrukowanie:

Hello World!
A String
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.