Jestem długoletnim programistą Java i wreszcie, po ukończeniu studiów, mam czas na przyzwoite przestudiowanie go, aby przystąpić do egzaminu certyfikacyjnego ... Jedną z rzeczy, które zawsze mnie niepokoiły, jest to, że String jest „ostateczny”. Rozumiem to, kiedy poczytam o kwestiach bezpieczeństwa i pokrewnych rzeczach ... Ale, na serio, czy ktoś ma na to prawdziwy przykład?
Na przykład, co by się stało, gdyby String nie był ostateczny? Jakby tego nie było w Ruby. Nie słyszałem żadnych skarg od społeczności Ruby ... I jestem świadomy StringUtils i pokrewnych klas, które musisz albo zaimplementować sam, albo przeszukać Internet, aby zaimplementować to zachowanie (4 linie kodu) chętnie.
java.io.File
nie byłofinal
. Och, nie jest. Facet.Odpowiedzi:
Główną przyczyną jest szybkość:
final
nie można rozszerzyć klas, co pozwoliło JIT na wszelkiego rodzaju optymalizacje podczas obsługi ciągów - nigdy nie trzeba sprawdzać przesłoniętych metod.Innym powodem jest bezpieczeństwo wątków: niezmienne są zawsze bezpieczne dla wątków, ponieważ wątek musi je całkowicie zbudować, zanim zostaną przekazane komuś innemu - a po zbudowaniu nie można ich już zmienić.
Ponadto twórcy środowiska wykonawczego Java zawsze chcieli się mylić po stronie bezpieczeństwa. Możliwość rozszerzenia String (coś, co często robię w Groovy, ponieważ jest to bardzo wygodne), może otworzyć całą puszkę robaków, jeśli nie wiesz, co robisz.
źródło
final
do szybkiego sprawdzenia, czy metoda nie jest przesłonięta podczas kompilacji kodu.final
słowo kluczowe w klasie. Tablica znaków, którą zawiera, jest ostateczna, dzięki czemu jest niezmienna. Ostateczne zamknięcie pętli przez klasę, jak wspomina @abl, ponieważ nie można wprowadzić zmiennej podklasy.Jest jeszcze jeden powód, dla którego
String
klasa Java musi być ostateczna: jest ważna dla bezpieczeństwa w niektórych scenariuszach. GdybyString
klasa nie była ostateczna, następujący kod byłby podatny na subtelne ataki złośliwego rozmówcy:Atak: złośliwy rozmówca może utworzyć podklasę String,
EvilString
gdzieEvilString.startsWith()
zawsze zwraca true, ale gdzie wartośćEvilString
jest czymś złym (npjavascript:alert('xss')
.). Ze względu na podklasę uniknęłoby to kontroli bezpieczeństwa. Atak ten znany jest jako podatność na przekroczenie limitu czasu użycia (TOCTTOU): między momentem sprawdzenia (od początku adresu URL http / https) a momentem użycia wartości (aby skonstruować fragment kodu HTML), efektywna wartość może się zmienić. GdybyString
nie było to ostateczne, ryzyko TOCTTOU byłoby wszechobecne.Jeśli więc
String
nie jest to ostateczne, trudno jest napisać bezpieczny kod, jeśli nie możesz zaufać swojemu rozmówcy. Oczywiście w takiej właśnie sytuacji znajdują się biblioteki Java: mogą być one wywoływane przez niezaufane aplety, więc nie mają odwagi zaufać swojemu programowi wywołującemu. Oznacza to, że napisanie bezpiecznego kodu biblioteki byłoby nierozsądnie trudne, gdybyString
nie było ostateczne.źródło
char[]
łańcucha, ale wymaga to trochę czasu.char[]
w ciągu; dlatego nie mogą wykorzystać luki w zabezpieczeniach TOCTTOU.String
nie byłoby to ostateczne. Tak długo, jak ten model zagrożenia jest istotny, staje się ważne, aby się przed nim bronić. O ile jest to ważne w przypadku apletów i innego niezaufanego kodu, jest to prawdopodobnie wystarczające uzasadnienieString
ostatecznego opracowania, nawet jeśli nie jest potrzebne w przypadku zaufanych aplikacji. (W każdym razie zaufane aplikacje mogą już ominąć wszystkie środki bezpieczeństwa, niezależnie od tego, czy są one ostateczne.)Jeśli nie, wielowątkowe aplikacje Java byłyby bałaganem (nawet gorzej niż w rzeczywistości).
IMHO Główną zaletą końcowych ciągów (niezmiennych) jest to, że są one z natury bezpieczne dla wątków: nie wymagają synchronizacji (pisanie tego kodu jest czasami dość trywialne, ale bardziej niż mniej niż to). Gdyby były zmienne, zagwarantowanie takich rzeczy, jak wielowątkowe zestawy narzędzi do systemu Windows byłoby bardzo trudne, a te rzeczy są absolutnie konieczne.
źródło
final
klasy nie mają nic wspólnego ze zmiennością klasy.Kolejną zaletą
String
bycia ostatecznym jest to, że zapewnia przewidywalne wyniki, podobnie jak niezmienność.String
, chociaż klasa ma być traktowana w kilku scenariuszach, takich jak typ wartości (kompilator nawet obsługuje ją przezString blah = "test"
). Dlatego jeśli utworzysz ciąg o określonej wartości, oczekujesz, że będzie on miał pewne zachowanie, które jest dziedziczone do dowolnego ciągu, podobnie do oczekiwań, jakie miałbyś w przypadku liczb całkowitych.Pomyśl o tym: co zrobić, jeśli podklasujesz
java.lang.String
i przesadzasz metodyequals
lubhashCode
? Nagle dwa „testy” struny nie mogły się już równać.Wyobraź sobie chaos, gdybym mógł to zrobić:
inne klasy:
źródło
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:
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ć innymjava.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:
Uzasadnieniem dla którego jest:
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.
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
achar*
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:
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.
źródło
Gdyby String nie był ostateczny, każda skrzynka narzędziowa programisty zawierałaby swój własny „String z kilkoma przyjemnymi metodami pomocniczymi”. Każda z nich byłaby niezgodna ze wszystkimi innymi skrzyniami narzędzi.
Uznałem to za głupie ograniczenie. Dziś jestem przekonany, że była to bardzo rozsądna decyzja. Utrzymuje ciągi jako ciągi znaków.
źródło
final
w Javie to nie to samo, cofinal
w C #. W tym kontekście jest to to samo, co C #readonly
. Zobacz stackoverflow.com/questions/1327544/…public final class String
.