tło
Są trzy miejsca, w których finał może pojawić się w Javie:
Ukończenie klasy uniemożliwia wszystkie podklasy klasy. Ukończenie metody zapobiega nadpisaniu jej przez podklasy metody. Ostateczne ustawienie pola zapobiega późniejszej zmianie.
Nieporozumienia
Istnieją optymalizacje, które mają miejsce wokół ostatecznych metod i pól.
Ostatnia metoda ułatwia HotSpotowi optymalizację poprzez wstawianie. Jednak HotSpot robi to, nawet jeśli metoda nie jest ostateczna, ponieważ działa przy założeniu, że nie została zastąpiona, dopóki nie udowodniono inaczej. Więcej o tym na SO
Ostateczną zmienną można agresywnie zoptymalizować, a więcej na ten temat można przeczytać w rozdziale 17.5.3 JLS .
Jednak przy takim zrozumieniu należy pamiętać, że żadna z tych optymalizacji nie ma na celu doprowadzenia do końca klasy . Ukończenie klasy nie przyniesie żadnego wzrostu wydajności.
Ostatni aspekt klasy nie ma też nic wspólnego z niezmiennością. Można mieć niezmienną klasę (taką jak BigInteger ), która nie jest ostateczna, lub klasę, która jest zmienna i końcowa (taka jak StringBuilder ). Decyzja o tym, czy klasa powinna być ostateczna, jest kwestią projektu.
Ostateczny projekt
Ciągi są jednym z najczęściej używanych typów danych. Znajdują się jako klucze do map, przechowują nazwy użytkowników i hasła, są tym, co czytasz z klawiatury lub pola na stronie internetowej. Ciągi są wszędzie .
Mapy
Pierwszą rzeczą do rozważenia, co by się stało, gdybyś mógł podklasować String, jest uświadomienie sobie, że ktoś mógłby zbudować zmienną klasę String, która w innym przypadku wyglądałaby na String. Spowodowałoby to zamieszanie w Mapach wszędzie.
Rozważ ten hipotetyczny kod:
Map t = new TreeMap<String, Integer>();
Map h = new HashMap<String, Integer>();
MyString one = new MyString("one");
MyString two = new MyString("two");
t.put(one, 1); h.put(one, 1);
t.put(two, 2); h.put(two, 2);
one.prepend("z");
Jest to problem z używaniem generalnie modyfikowalnego klucza z Mapą, ale staram się tam znaleźć to, że nagle kilka rzeczy związanych z łamaniem Mapy. Wejście nie znajduje się już we właściwym miejscu na mapie. W HashMap wartość skrótu zmieniła się (powinna była) i dlatego nie jest już na właściwym wejściu. W TreeMap drzewo jest teraz zepsute, ponieważ jeden z węzłów znajduje się po niewłaściwej stronie.
Ponieważ używanie ciągu dla tych kluczy jest tak powszechne, należy temu zapobiec, czyniąc ciąg końcowym.
Być może zainteresuje Cię lektura Dlaczego String jest niezmienny w Javie? po więcej informacji o niezmiennej naturze Strun.
Niecne struny
Istnieje wiele różnych niecnych opcji dla ciągów. Zastanów się, czy utworzyłem ciąg, który zawsze zwracał wartość true, gdy wywołano równość ... i przekazałem go do sprawdzenia hasła? A może sprawiło, że przypisania do MyString wysyłałyby kopię ciągu na jakiś adres e-mail?
Są to bardzo realne możliwości, gdy masz możliwość podklasy String.
Java.lang Optymalizacje ciągów
Chociaż wcześniej wspominałem, że finał nie przyspiesza Sznurka. Jednak klasa String (i inne klasy w java.lang
) często korzystają z ochrony pól i metod na poziomie pakietu, aby umożliwić innym java.lang
klasom majstrowanie przy elementach wewnętrznych zamiast ciągłego korzystania z publicznego interfejsu API dla String. Funkcje takie jak getChars bez sprawdzania zakresu lub lastIndexOf, który jest używany przez StringBuffer, lub konstruktor, który dzieli podstawową tablicę (zwróć uwagę, że jest to rzecz Java 6, która została zmieniona z powodu problemów z pamięcią).
Gdyby ktoś stworzył podklasę String, nie byłby w stanie udostępnić tych optymalizacji (chyba że byłby to również część java.lang
, ale jest to zapieczętowany pakiet ).
Trudniej jest zaprojektować coś do rozszerzenia
Projektowanie czegoś, co ma być rozszerzalne, jest trudne . Oznacza to, że musisz ujawnić części swoich wewnętrznych elementów, aby coś innego można było zmodyfikować.
Rozszerzalny ciąg nie mógł naprawić przecieku pamięci . Te części musiałyby być narażone na podklasy, a zmiana tego kodu oznaczałaby, że podklasy uległyby awarii.
Java szczyci się kompatybilnością wsteczną i otwierając podstawowe klasy na rozszerzenia, traci się część tej zdolności do naprawiania rzeczy przy jednoczesnym zachowaniu obliczalności w podklasach stron trzecich.
Checkstyle ma regułę, która wymusza (co naprawdę frustruje mnie podczas pisania kodu wewnętrznego) o nazwie „DesignForExtension”, która wymusza, że każda klasa to:
- Abstrakcyjny
- Finał
- Puste wdrożenie
Uzasadnieniem dla którego jest:
Ten styl projektowania API chroni nadklasy przed podziałem na podklasy. Wadą jest to, że podklasy mają ograniczoną elastyczność, w szczególności nie mogą zapobiec wykonywaniu kodu w nadklasie, ale oznacza to również, że podklasy nie mogą uszkodzić stanu nadklasy poprzez zapomnienie o wywołaniu metody super.
Zezwolenie na rozszerzenie klas implementacji oznacza, że podklasy mogą zepsuć stan klasy, na której są oparte, i sprawić, że różne gwarancje, które daje nadklasa, są nieważne. Czegoś tak skomplikowane jak String, to jest prawie pewne, że zmiana jego część będzie złamać coś.
Deweloperów
Jest to część bycia programistą. Rozważyć prawdopodobieństwo, że każdy programista stworzą własne podklasy String z własnej kolekcji utils w nim. Ale teraz tych podklas nie można swobodnie przypisywać sobie wzajemnie.
WleaoString foo = new WleaoString("foo");
MichaelTString bar = foo; // This doesn't work.
Ta droga prowadzi do szaleństwa. Casting do String wszędzie i sprawdzając, czy ciąg jest instancją swojej klasy String lub jeśli nie, co czyni nowy ciąg na podstawie tego jednego i ... po prostu nie. Nie rób
Jestem pewien, że można napisać dobrą klasę String ... ale zostawić pisanie wielu implementacji ciąg do tych szalonych ludzi, którzy piszą w C ++ i mają do czynienia z std::string
a char*
i coś z doładowania i SString i całej reszty .
Java String Magic
Istnieje kilka magicznych rzeczy, które Java robi ze Strings. Ułatwiają one programistom radzenie sobie, ale wprowadzają pewne niespójności w języku. Zezwalanie na podklasy w Stringach wymagałoby bardzo ważnej przemyślenia, jak radzić sobie z tymi magicznymi rzeczami:
Literały łańcuchowe (JLS 3.10.5 )
Posiadanie kodu, który pozwala na:
String foo = "foo";
Nie należy tego mylić z boksem typów liczbowych, takich jak liczba całkowita. Nie możesz 1.toString()
, ale możesz "foo".concat(bar)
.
+
Operatora (na trasę 15.18.1 )
Żaden inny typ odwołania w Javie nie pozwala na użycie operatora. Ciąg jest wyjątkowy. Operator konkatenacji łańcuchów działa również na poziomie kompilatora, dzięki czemu "foo" + "bar"
staje się "foobar"
on bardziej kompilowany niż w czasie wykonywania.
Konwersja ciągów (JLS 5.1.11 )
Wszystkie obiekty można konwertować na ciągi, używając ich w kontekście ciągów.
String Interning ( JavaDoc )
Klasa String ma dostęp do puli ciągów, która pozwala na kanoniczne reprezentacje obiektu wypełnionego w typie kompilacji literałami ciągu.
Zezwalanie na podklasę String oznaczałoby, że te bity z String, które ułatwiają programowanie, stałyby się bardzo trudne lub niemożliwe, gdy możliwe są inne typy String.