Jaki jest sens „ostatniej klasy” w Javie?


569

Czytam książkę o Javie i mówi, że możesz zadeklarować całą klasę jako final. Nie mogę wymyślić niczego, gdzie bym tego użył.

Jestem nowy w programowaniu i zastanawiam się, czy programiści faktycznie używają tego w swoich programach . Jeśli tak, to kiedy go używają, abym mógł lepiej to zrozumieć i wiedzieć, kiedy go użyć.

Jeśli Java jest zorientowana obiektowo, a ty deklarujesz klasę final, to czy nie oznacza to, że klasa ma cechy obiektów?

Odpowiedzi:


531

Przede wszystkim polecam ten artykuł: Java: Kiedy stworzyć ostateczną klasę


Jeśli tak, to kiedy go używają, abym mógł lepiej to zrozumieć i wiedzieć, kiedy go użyć.

finalKlasa jest po prostu klasa, która nie może zostać przedłużony .

(Nie oznacza to, że wszystkie odwołania do obiektów tej klasy działałyby tak, jakby były zadeklarowane jako final.)

Gdy przydatne jest zadeklarowanie klasy jako końcowej, jest ona uwzględniona w odpowiedziach na to pytanie:

Jeśli Java jest zorientowana obiektowo, a ty deklarujesz klasę final, to czy nie oznacza to, że klasa ma cechy obiektów?

W pewnym sensie tak.

Oznaczając klasę jako końcową, wyłączasz potężną i elastyczną funkcję języka dla tej części kodu. Niektóre klasy nie powinny jednak (a w niektórych przypadkach nie mogą ) być zaprojektowane w taki sposób, aby odpowiednio uwzględnić podklasę. W takich przypadkach sensowne jest oznaczenie klasy jako ostatecznej, mimo że ogranicza ona OOP. (Pamiętaj jednak, że klasa końcowa może nadal przedłużyć inną klasę niefinałową).


39
Aby dodać do odpowiedzi, jedną z zasad Efektywnej Javy jest faworyzowanie kompozycji nad dziedziczeniem. Zastosowanie końcowego słowa kluczowego pomaga również egzekwować tę zasadę.
Riggy

9
„Robisz to głównie ze względów wydajności i bezpieczeństwa”. Często słyszę tę uwagę (nawet Wikipedia), ale nadal nie rozumiem uzasadnienia tego argumentu. Czy ktoś chce wyjaśnić, jak, powiedzmy, nieokreślony plik java.lang.String skończyłby się nieefektywnym lub niepewnym?
MRA

27
@MRA Jeśli utworzę metodę, która akceptuje ciąg jako parametr, zakładam, że jest niezmienny, ponieważ ciągi są. W rezultacie wiem, że mogę bezpiecznie wywoływać dowolną metodę na obiekcie String i nie zmieniać przekazywanego ciągu String. Jeśli miałbym rozszerzyć String i zmienić implementację podłańcuchu, aby zmienić rzeczywisty String, to obiekt String, który miał być niezmienny, nie jest już niezmienny.
Cruncher

1
@Sortofabeginner I gdy tylko powiesz, że chcesz, aby wszystkie metody i pola String były ostateczne, po prostu możesz stworzyć klasę z dodatkowymi funkcjami ... W tym momencie równie dobrze możesz po prostu stworzyć klasę, która ma łańcuch i tworzyć metody działające na tym ciągu.
Cruncher

1
@Shay final (między innymi) służy do unieruchomienia obiektu, więc nie powiedziałbym, że nie mają ze sobą nic wspólnego. Zobacz tutaj docs.oracle.com/javase/tutorial/essential/concurrency/…
Celeritas

184

W Javie elementów z finalmodyfikatorem nie można zmienić!

Obejmuje to końcowe klasy, końcowe zmienne i końcowe metody:

  • Ostateczna klasa nie może być przedłużona o żadną inną klasę
  • Ostatecznej zmiennej nie można przypisać innej wartości
  • Ostatecznej metody nie można zastąpić

40
Właściwe pytanie brzmi: dlaczego , a nie co .
Francesco Menzani,

8
Stwierdzenie „W Javie elementów z finalmodyfikatorem nie można zmienić!” Jest zbyt kategoryczne i w rzeczywistości nie do końca poprawne. Jak to ujął Grady Booch: „Obiekt ma stan, zachowanie i tożsamość”. Chociaż nie możemy zmienić tożsamości obiektu, gdy odniesienie zostanie oznaczone jako ostateczne, mamy szansę zmienić jego stan poprzez przypisanie nowych wartości do finalpól innych (pod warunkiem, że oczywiście je ma). Każdy, kto jest planując uzyskanie certyfikatu Oracle Java (takiego jak 1Z0-808 itp.) należy o tym pamiętać, ponieważ na egzaminie mogą pojawić się pytania dotyczące tego aspektu ...
Igor Soudakevitch,

33

Jeden scenariusz, w którym ze względów bezpieczeństwa ważna jest ostateczność, gdy chcesz zapobiec dziedziczeniu klasy. Pozwala to upewnić się, że uruchomiony kod nie jest przez nikogo nadpisywany .

Kolejny scenariusz dotyczy optymalizacji: wydaje mi się, że pamiętam, że kompilator Java zawiera niektóre wywołania funkcji z klas końcowych. Jeśli więc zadzwonisz, a.x()a a zostanie zadeklarowane final, wiemy w czasie kompilacji, jaki będzie kod i można go włączyć do funkcji wywołującej. Nie mam pojęcia, czy rzeczywiście tak się dzieje, ale ostatecznie jest taka możliwość.


7
Inlining jest zwykle wykonywany tylko przez kompilator just-in-time w czasie wykonywania. Działa również bez wersji ostatecznej, ale kompilator JIT musi wykonać trochę więcej pracy, aby mieć pewność, że nie ma klas rozszerzających (lub że te klasy rozszerzające nie dotykają tej metody).
Paŭlo Ebermann

Dobry zapis na temat inlinizacji i optymalizacji można znaleźć tutaj: lemire.me/blog/archives/2014/12/17/…
Josh Hemann

24

Najlepszym przykładem jest

publiczna klasa końcowa Ciąg

która jest niezmienną klasą i nie może być przedłużona. Oczywiście jest coś więcej niż uczynienie klasy ostatecznej niezmienną.


Hehe, czasami chroni programistów Rube Goldergian przed sobą.
Zoidberg,

16

Odpowiednia lektura: Zasada otwartego i zamkniętego autorstwa Boba Martina.

Kluczowy cytat:

Elementy oprogramowania (klasy, moduły, funkcje itp.) Powinny być otwarte dla rozszerzenia, ale zamknięte dla modyfikacji.

Słowo finalkluczowe służy do wymuszenia tego w Javie, niezależnie od tego, czy jest używane w metodach, czy w klasach.


6
@Sean: Czy deklarowanie go nie finalpowoduje, że klasa jest zamknięta dla rozszerzenia, a nie otwarta? Czy też biorę to zbyt dosłownie?
Goran Jovic

4
@Goran na całym świecie stosuje ostateczne, tak. Kluczem jest selektywne zastosowanie ostateczne w miejscach, w których nie chcesz modyfikacji (i oczywiście zapewnienie dobrych haczyków dla rozszerzenia)
Sean Patrick Floyd

26
W OCP „modyfikacja” odnosi się do modyfikacji kodu źródłowego, a „rozszerzenie” odnosi się do dziedziczenia implementacji. Dlatego użycie finaldeklaracji klasy / metody nie ma sensu, jeśli chcesz, aby kod implementacji został zamknięty w celu modyfikacji, ale otwarty do rozszerzenia przez dziedziczenie.
Rogério,

1
@Rogerio Pożyczałem referencję (i interpretację) z Spring Framework Reference (MVC) . IMHO ma to o wiele większy sens niż oryginalna wersja.
Sean Patrick Floyd

Rozszerzenie nie działa. Nieprzydatny. Zdziesiątkowany. Zniszczony. Nie obchodzi mnie OCP. Nigdy nie ma wymówki, aby przedłużyć klasę.
Josh Woodcock

15

Jeśli wyobrażasz sobie hierarchię klas jako drzewo (jak w Javie), klasy abstrakcyjne mogą być tylko gałęziami, a klasy końcowe to te, które mogą być tylko liśćmi. Klasy, które nie należą do żadnej z tych kategorii, mogą być zarówno gałęziami, jak i liśćmi.

Tu nie ma naruszenia zasad OO, finał po prostu zapewnia niezłą symetrię.

W praktyce chcesz użyć opcji końcowej, jeśli chcesz, aby Twoje obiekty były niezmienne lub jeśli piszesz interfejs API, aby zasygnalizować użytkownikom interfejsu API, że klasa nie jest przeznaczona do rozszerzenia.


13

Samo słowo kluczowe finaloznacza, że ​​coś jest ostateczne i nie powinno być w żaden sposób modyfikowane. Jeśli klasa jest oznaczona, finalnie można jej rozszerzyć ani podklasować. Ale pytanie brzmi: dlaczego oceniamy klasę final? IMO ma różne przyczyny:

  1. Standaryzacja: Niektóre klasy wykonują standardowe funkcje i nie są przeznaczone do modyfikacji, np. Klasy wykonujące różne funkcje związane z manipulowaniem łańcuchami lub funkcjami matematycznymi itp.
  2. Względy bezpieczeństwa : czasami piszemy klasy, które wykonują różne funkcje uwierzytelniania i hasła i nie chcemy, aby ktokolwiek inny je zmieniał.

Słyszałem, że ocena klasy finalpoprawia wydajność, ale szczerze mówiąc, nie mogłam znaleźć tego argumentu, który miałby dużą wagę.

Jeśli Java jest zorientowana obiektowo, a deklarujesz klasę jako ostateczną, to czy nie oznacza to, że klasa ma cechy obiektów?

Być może tak, ale czasem taki jest zamierzony cel. Czasami robimy to, aby osiągnąć większe korzyści z bezpieczeństwa itp., Poświęcając zdolność tej klasy do rozszerzenia. Ale klasa końcowa może w razie potrzeby rozszerzyć jedną klasę.

Z drugiej strony powinniśmy preferować kompozycję zamiast dziedziczenia, a finalsłowo kluczowe faktycznie pomaga w egzekwowaniu tej zasady.


6

Bądź ostrożny, kiedy uczynisz klasę „końcową”. Ponieważ jeśli chcesz napisać test jednostkowy dla końcowej klasy, nie możesz podklasować tej końcowej klasy, aby zastosować technikę rozbijania zależności „Metoda podklasy i przesłonięcia” opisana w książce Michaela C. Feathersa „Skuteczna praca ze starszym kodem” . W tej książce Feathers powiedział: „Poważnie, łatwo jest uwierzyć, że zapieczętowane i ostateczne są błędnym błędem, że nigdy nie powinny były być dodawane do języków programowania. Ale prawdziwa wina leży po naszej stronie. Gdy polegamy bezpośrednio na biblioteki, które są poza naszą kontrolą, po prostu prosimy o kłopoty ”.


6

final class można uniknąć zepsucia publicznego interfejsu API podczas dodawania nowych metod

Załóżmy, że w wersji 1 swojej Baseklasy robisz:

public class Base {}

a klient:

class Derived extends Base {
    public int method() { return 1; }
}

Następnie, jeśli w wersji 2 chcesz dodać methodmetodę do Base:

class Base {
    public String method() { return null; }
}

zepsułby kod klienta.

Gdybyśmy zastosowali final class Basezamiast tego, klient nie byłby w stanie dziedziczyć, a dodanie metody nie spowodowałoby uszkodzenia interfejsu API.


5

Jeśli klasa jest oznaczona final, oznacza to, że jej struktura nie może być modyfikowana przez nic zewnętrznego. Tam, gdzie jest to najbardziej widoczne, kiedy wykonujesz tradycyjne dziedziczenie polimorficzne, w zasadzie class B extends Apo prostu nie zadziała. Jest to w zasadzie sposób ochrony niektórych części twojego kodu (do pewnego stopnia) .

Aby wyjaśnić, oznaczanie klasy finalnie oznacza jej pól jako finaltakich i jako takie nie chroni właściwości obiektu, ale rzeczywistą strukturę klasy.


1
Co oznaczają właściwości obiektu? Czy to oznacza, że ​​mógłbym zmodyfikować zmienną składową klasy, jeśli klasa zostanie uznana za końcową? Zatem jedynym celem ostatniej klasy jest zapobieganie dziedziczeniu.
Adam Lyu,

5

ABY ROZWIĄZYWAĆ PROBLEM Z KLASĄ KOŃCOWĄ:

Istnieją dwa sposoby na zakończenie klasy. Pierwszym z nich jest użycie słowa kluczowego final w deklaracji klasy:

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

Drugim sposobem na zakończenie klasy jest zadeklarowanie wszystkich jej konstruktorów jako prywatnych:

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

Oznaczenie go jako końcowego oszczędza kłopotów, jeśli dowiesz się, że jest to rzeczywiście finał, aby zademonstrować spojrzenie na tę klasę testu. na pierwszy rzut oka wygląda publicznie.

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

Niestety, ponieważ jedyny konstruktor klasy jest prywatny, nie można rozszerzyć tej klasy. W przypadku klasy Test nie ma powodu, aby klasa była ostateczna. Klasa Test jest dobrym przykładem tego, w jaki sposób niejawne klasy końcowe mogą powodować problemy.

Powinieneś więc oznaczyć go jako ostateczny, gdy domyślnie uczynisz klasę końcową, czyniąc ją konstruktorem prywatną.


4

Klasa końcowa to klasa, której nie można przedłużyć. Również metody można zadeklarować jako ostateczne, aby wskazać, że nie można ich zastąpić podklasami.

Zapobieganie podzklasowaniu klasy może być szczególnie przydatne, jeśli piszesz interfejsy API lub biblioteki i chcesz uniknąć rozszerzenia w celu zmiany podstawowego zachowania.


4

Jedna zaleta utrzymania klasy jako ostatecznej:

Klasa string jest utrzymywana jako ostateczna, aby nikt nie mógł przesłonić jej metod i zmienić funkcjonalności. np. nikt nie może zmienić funkcjonalności metody length (). Zawsze zwróci długość łańcucha.

Deweloper tej klasy nie chciał, aby ktokolwiek zmienił funkcjonalność tej klasy, więc utrzymał ją jako ostateczną.



3

W języku Java ostateczne użycie słowa kluczowego w poniższych przypadkach.

  1. Ostateczne zmienne
  2. Ostateczne metody
  3. Klasy końcowe

W języku Java zmienne końcowe nie mogą być ponownie przypisywane, klasy końcowe nie mogą się rozszerzać, a metody końcowe nie mogą zastępować.


1

Klasy końcowe nie mogą być przedłużane. Jeśli więc chcesz, aby klasa zachowywała się w określony sposób i nie pozwalała komuś przesłonić metod (z możliwie mniej wydajnym i bardziej złośliwym kodem), możesz zadeklarować całą klasę jako ostateczne lub specyficzne metody, którymi nie chcesz być zmienione.

Ponieważ zadeklarowanie klasy nie uniemożliwia utworzenia instancji klasy, nie oznacza to, że powstrzyma ona klasę od posiadania właściwości obiektu. Trzeba tylko trzymać się metod dokładnie tak, jak są zadeklarowane w klasie.


1

myślę, że FINAL to „koniec linii” - ten facet nie może już produkować potomstwa. Więc kiedy spojrzysz na to w ten sposób, zobaczysz mnóstwo scenariuszy ze świata rzeczywistego, które napotkasz, a będziesz musiał oznaczyć klasę znacznikiem „końca linii”. Jest to projekt oparty na domenie - jeśli twoja domena wymaga, aby dana ENTITY (klasa) nie mogła tworzyć podklas, oznacz ją jako FINAL.

Powinienem zauważyć, że nic nie powstrzymuje cię przed odziedziczeniem klasy „należy oznaczyć jako ostateczną”. Ale jest to ogólnie klasyfikowane jako „nadużycie dziedziczenia” i dzieje się tak, ponieważ najczęściej chciałbyś odziedziczyć jakąś funkcję z klasy podstawowej w swojej klasie.

Najlepszym podejściem jest przyjrzenie się domenie i pozwolenie, by podyktowało twoje decyzje projektowe.


1

Jak powiedziano powyżej, jeśli chcesz, aby nikt nie mógł zmienić funkcjonalności metody, możesz zadeklarować ją jako ostateczną.

Przykład: Ścieżka pliku serwera aplikacji do pobierania / wysyłania, podział łańcucha na podstawie przesunięcia, takie metody można zadeklarować jako końcowe, aby te funkcje metod nie uległy zmianie. A jeśli chcesz takich metod końcowych w osobnej klasie, zdefiniuj tę klasę jako klasę końcową. Tak więc klasa końcowa będzie miała wszystkie metody końcowe, przy czym jako metoda końcowa może być zadeklarowana i zdefiniowana w klasie niefunkcjonalnej.



1

Powiedzmy, że masz Employeeklasę, która ma metodę greet. Po greetwywołaniu metody po prostu drukuje Hello everyone!. Więc to jest oczekiwane zachowanie od greetmetody

public class Employee {

    void greet() {
        System.out.println("Hello everyone!");
    }
}

Teraz pozwól, aby GrumpyEmployeepodklasa Employeei greetmetoda zastąpiły , jak pokazano poniżej.

public class GrumpyEmployee extends Employee {

    @Override
    void greet() {
        System.out.println("Get lost!");
    }
}

Teraz w poniższym kodzie spójrz na sayHellometodę. Pobiera Employeeinstancję jako parametr i wywołuje metodę powitania z nadzieją, że powie: Hello everyone!Ale dostajemy to Get lost!. Ta zmiana zachowania wynika zEmployee grumpyEmployee = new GrumpyEmployee();

public class TestFinal {
    static Employee grumpyEmployee = new GrumpyEmployee();

    public static void main(String[] args) {
        TestFinal testFinal = new TestFinal();
        testFinal.sayHello(grumpyEmployee);
    }

    private void sayHello(Employee employee) {
        employee.greet(); //Here you would expect a warm greeting, but what you get is "Get lost!"
    }
}

Tej sytuacji można uniknąć, jeśli Employeeklasa została stworzona final. Wyobraź sobie, jaki chaos może wywołać bezczelny programista, jeśli StringClass nie zostanie zadeklarowany jako final.


1

Klasy końcowej nie można dalej przedłużać. Jeśli nie musimy uczynić klasy dziedziczoną w Javie, możemy zastosować to podejście.

Jeśli musimy po prostu stworzyć określone metody w klasie, aby ich nie zastąpić, możemy po prostu umieścić przed nimi końcowe słowo kluczowe. Tam klasa jest nadal dziedziczna.


-1

Orientacja obiektowa nie polega na dziedziczeniu, chodzi o enkapsulację. Dziedziczenie przerywa enkapsulację.

W wielu przypadkach ogłoszenie finału klasy ma sens. Każdy przedmiot reprezentujący „wartość”, taki jak kolor lub kwota pieniężna, może być ostateczny. Stają na własną rękę.

Jeśli piszesz biblioteki, spraw, aby twoje zajęcia były ostateczne, chyba że wyraźnie włączysz je, aby je wyprowadzić. W przeciwnym razie ludzie mogą wywnioskować twoje klasy i zastąpić metody, łamiąc twoje założenia / niezmienniki. Może to mieć również wpływ na bezpieczeństwo.

Joshua Bloch w „Skutecznej Javie” zaleca jawne projektowanie dziedziczenia lub zabranianie go i zauważa, że ​​projektowanie dziedziczenia nie jest takie łatwe.

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.