Odkąd dowiedziałem się, że zajęcia java.lang.String
są deklarowane jako końcowe w Javie, zastanawiałem się, dlaczego tak jest. Nie znalazłem wtedy żadnej odpowiedzi, ale ten post: Jak stworzyć replikę klasy String w Javie? przypomniał mi o moim pytaniu.
Jasne, String zapewnia wszystkie funkcje, których kiedykolwiek potrzebowałem i nigdy nie pomyślałem o żadnej operacji, która wymagałaby rozszerzenia klasy String, ale nigdy nie będziesz wiedział, czego ktoś może potrzebować!
Czy zatem ktoś wie, jaki był zamiar projektantów, gdy zdecydowali się na ostateczną wersję?
Odpowiedzi:
Implementacja łańcuchów znaków jako niezmiennych obiektów jest bardzo przydatna . Przeczytaj o niezmienności, aby dowiedzieć się więcej na ten temat.
Jedną z zalet niezmiennych obiektów jest to
( stąd ).
Gdyby łańcuch znaków nie był ostateczny, można by utworzyć podklasę i mieć dwa ciągi, które wyglądają podobnie, gdy są „widziane jako ciągi znaków”, ale w rzeczywistości są różne.
źródło
To fajny artykuł, który przedstawia dwa powody, o których już wspomniano w powyższych odpowiedziach:
I to jest prawdopodobnie najbardziej szczegółowy komentarz w tym artykule. Ma to związek z pulą ciągów w Javie i kwestiami bezpieczeństwa. Chodzi o to, jak zdecydować, co trafia do puli strun. Zakładając, że oba ciągi znaków są równe, jeśli ich sekwencja znaków jest taka sama, wówczas mamy warunek wyścigu określający, kto dotrze tam pierwszy, a wraz z nim kwestie bezpieczeństwa. Jeśli nie, to pula ciągów będzie zawierała zbędne ciągi, co spowoduje utratę korzyści z posiadania jej w pierwszej kolejności. Po prostu przeczytaj to sobie, dobrze?
Rozszerzanie String mogłoby siać spustoszenie wśród równych sobie i stażystów. JavaDoc mówi, że równa się:
Zakładając, że
java.lang.String
nie było ostateczne, aSafeString
może równać się aString
i na odwrót; ponieważ reprezentowałyby tę samą sekwencję znaków.Co by się stało, gdybyś zgłosił się
intern
doSafeString
- czySafeString
trafiłby do puli stringów JVM?ClassLoader
I wszystkich obiektowSafeString
referencje uznane byłyby następnie zablokowane na miejscu przez cały okres JVM. Otrzymasz stan wyścigu o tym, kto może być pierwszym, który internuje sekwencję postaci - możeSafeString
wygrałbyś, możeString
, a możeSafeString
załadowany przez inny program ładujący (a więc inną klasę).Jeśli wygrałeś wyścig w puli, byłby to prawdziwy singleton, a ludzie mogliby uzyskać dostęp do całego twojego środowiska (piaskownicy) poprzez refleksję i
secretKey.intern().getClass().getClassLoader()
.JVM może też zablokować tę dziurę, upewniając się, że do puli zostały dodane tylko konkretne obiekty typu String (bez podklas).
Jeśli equals zostało zaimplementowane w taki sposób, że
SafeString
! =String
ThenSafeString.intern
! =String.intern
ISafeString
musiałoby zostać dodane do puli. Pula stałaby się wtedy pulą<Class, String>
zamiast,<String>
a wszystko, czego potrzebujesz, aby wejść do puli, to nowy program ładujący klasy.źródło
Absolutnie najważniejszym powodem, dla którego String jest niezmienny lub ostateczny, jest to, że jest używany przez mechanizm ładowania klas, a zatem ma głębokie i podstawowe aspekty bezpieczeństwa.
Gdyby parametr String był zmienny lub nie był ostateczny, żądanie załadowania „java.io.Writer” mogłoby zostać zmienione na załadowanie „mil.vogoon.DiskErasingWriter”
odniesienie: Dlaczego ciąg znaków jest niezmienny w Javie
źródło
String
jest bardzo podstawową klasą w Javie, wiele rzeczy polega na tym, że działa w określony sposób, na przykład jest niezmienna.Utworzenie klasy
final
zapobiega podklasom, które mogłyby złamać te założenia.Zauważ, że nawet teraz, jeśli używasz odbicia, możesz przerwać ciągi (zmienić ich wartość lub kod skrótu). Refleksję można zatrzymać za pomocą menedżera ds. Bezpieczeństwa. Gdyby
String
nie byłofinal
, każdy mógłby to zrobić.Inne klasy, które nie są zadeklarowane,
final
pozwalają na zdefiniowanie nieco uszkodzonych podklas (na przykład możesz miećList
dodaną do niewłaściwej pozycji), ale przynajmniej JVM nie jest zależny od tych klas w swoich podstawowych operacjach.źródło
final
na klasie nie gwarantuje niezmienności. Gwarantuje tylko, że niezmienniki klasy (z których jeden może być niezmiennością) nie mogą zostać zmienione przez podklasę.Jak powiedział Bruno, chodzi o niezmienność. Nie chodzi tylko o ciągi, ale także o wszelkie opakowania, np. Double, Integer, Character, itp. Jest wiele powodów takiego stanu rzeczy:
Zasadniczo to, abyś jako programista mógł być pewien, że Twój łańcuch nigdy nie zostanie zmieniony. Jeśli wiesz, jak to działa, to również może poprawić zarządzanie pamięcią. Spróbuj utworzyć dwa identyczne ciągi jeden po drugim, na przykład „witaj”. Jeśli debugujesz, zauważysz, że mają identyczne identyfikatory, co oznacza, że są to dokładnie TEGO SAME obiekty. Wynika to z faktu, że Java pozwala ci to zrobić. Nie byłoby to możliwe, gdyby struny były zmienne. Mogą mieć to samo „ja” itp., Ponieważ nigdy się nie zmienią. Więc jeśli kiedykolwiek zdecydujesz się utworzyć 1 000 000 ciągów znaków „cześć”, tak naprawdę utwórz 1 000 000 wskaźników na „cześć”. Podobnie, połączenie dowolnej funkcji w łańcuchu lub jakichkolwiek opakowań z tego powodu spowodowałoby utworzenie innego obiektu (ponownie spójrz na identyfikator obiektu - zmieni się).
Dodatkowo finał w Javie niekoniecznie oznacza, że obiekt nie może się zmienić (różni się np. Od C ++). Oznacza to, że adres, na który wskazuje, nie może się zmienić, ale nadal możesz zmienić jego właściwości i / lub atrybuty. Zatem zrozumienie różnicy między niezmiennością a ostatecznością w niektórych przypadkach może być naprawdę ważne.
HTH
Bibliografia:
źródło
Być może chodziło o uproszczenie wdrażania. Jeśli projektujesz klasę, która będzie dziedziczona przez użytkowników tej klasy, masz do rozważenia cały nowy zestaw przypadków użycia. Co się stanie, jeśli zrobią to lub tamto z polem chronionym X? Robiąc to ostatecznie, mogą skupić się na poprawnym działaniu interfejsu publicznego i upewnić się, że jest solidny.
źródło
Mając wiele dobrych punktów, które zostały już wspomniane, chciałbym dodać kolejny jeden z powodów, dla których String jest niezmienny w Javie, polega na umożliwieniu Stringowi buforowania swojego kodu skrótu , będąc niezmiennym String w Javie buforuje swój kod skrótu i nie oblicza wszystkich raz wywołujemy metodę hashcode typu String , co sprawia, że jest ona bardzo szybka jak klucz hashmap do użycia w hashmap w Javie.
Krótko mówiąc, ponieważ String jest niezmienny, nikt nie może zmienić jego zawartości po utworzeniu, co gwarantuje, że hashCode String będzie taki sam przy wielu wywołaniach.
Jeśli widzisz,
String
klasa ma jest zadeklarowana jakoa
hashcode()
funkcja jest następująca -Jeśli jest to już komputer, po prostu zwróć wartość.
źródło
Aby mieć pewność, że nie otrzymamy lepszej implementacji. Powinien to być oczywiście interfejs.
[edytuj] Ach, coraz więcej głosów w dół. Odpowiedź jest całkowicie poważna. Musiałem kilkakrotnie programować obejście głupiej implementacji String, co doprowadziło do poważnej utraty wydajności i produktywności
źródło
Oprócz oczywistych powodów sugerowanych w innych odpowiedziach, jedna myśl o uczynieniu klasy String ostateczną może być również związana z narzutem wydajności metod wirtualnych. Pamiętaj, że String jest klasą ciężką, co sprawia, że ta ostateczna implementacja oznacza na pewno brak pod-implementacji, co oznacza brak narzutu wywołań pośrednich. Oczywiście teraz mamy takie rzeczy jak wirtualne wywołanie i inne, które zawsze wykonują tego rodzaju optymalizację za Ciebie.
źródło
Oprócz powodów wymienionych w innych odpowiedziach (bezpieczeństwo, niezmienność, wydajność) należy zauważyć, że
String
ma specjalne wsparcie językowe. Możesz pisaćString
literały i jest wsparcie dla+
operatora. Zezwolenie programistom na tworzenie podklasString
zachęcałoby do włamań, takich jak:źródło
Cóż, mam trochę inny myśli, że nie jestem pewien, czy mam rację, czy nie, ale w Java String jest jedynym obiektem, który może być traktowany jako prymitywne typu danych, a mam na myśli możemy stworzyć obiekt String jako String name = "java ” . Teraz, podobnie jak inne prymitywne typy danych, które są kopiowane przez wartość, a nie przez odniesienie, oczekuje się, że String będzie miał takie samo zachowanie, dlatego String jest ostateczny. Tak właśnie myślałem. Proszę zignorować, jeśli jest to całkowicie nielogiczne.
źródło
Ostateczność strun również broni ich jako standard. W C ++ możesz tworzyć podklasy stringów, więc każdy sklep programistyczny może mieć własną wersję stringów. Prowadziłoby to do braku mocnych standardów.
źródło
Powiedzmy, że masz
Employee
klasę, która ma metodęgreet
. Kiedygreet
metoda jest wywoływana, po prostu drukujeHello everyone!
. Więc to jest oczekiwane zachowanie odgreet
metodyTeraz niech
GrumpyEmployee
podklasaEmployee
i przesłonięciegreet
metody, jak pokazano poniżej.Teraz w poniższym kodzie spójrz na
sayHello
metodę. PrzyjmujeEmployee
instancję jako parametr i wywołuje metodę greet z nadzieją, że powieHello everyone!
Ale to, co otrzymujemy, toGet lost!
. Ta zmiana w zachowaniu jest spowodowanaEmployee grumpyEmployee = new GrumpyEmployee();
Sytuacji tej można uniknąć, jeśli
Employee
zajęcia zostały wykonanefinal
. Teraz to zależy od twojej wyobraźni, jaki chaos mógłby spowodować bezczelny programista, gdybyString
Class nie został zadeklarowany jakofinal
.źródło
Czy JVM wie, co jest niezmienne? Odpowiedź brzmi: Nie, stała pula zawiera wszystkie niezmienne pola, ale wszystkie niezmienne pola / obiekty nie są przechowywane tylko w stałej puli. Tylko my wdrażamy go w taki sposób, aby osiągnął niezmienność i swoje cechy. CustomString mógłby zostać zaimplementowany bez finalizacji przy użyciu MarkerInterface, który zapewniłby specjalne zachowanie java dla jego puli, ta funkcja jest nadal oczekiwana!
źródło
Większość odpowiedzi dotyczy niezmienności - dlaczego obiekt typu String nie może być aktualizowany w miejscu. Jest tu wiele dobrych dyskusji, a społeczność Javy zrobiłaby dobrze, gdyby przyjęła niezmienność jako zasadę. (Nie wstrzymuję oddechu.)
Jednak pytanie PO dotyczy tego, dlaczego jest ostateczny - dlaczego nie można go przedłużyć. Niektórzy tutaj podjęli się tego, ale zgodziłbym się z PO, że jest tu prawdziwa luka. Inny język pozwala programistom tworzyć nowe typy nominalne dla typu. Na przykład w Haskell mogę utworzyć następujące nowe typy, które są identyczne w czasie wykonywania jak tekst, ale zapewniają bezpieczeństwo wiązania w czasie kompilacji.
Dlatego przedstawiłbym następującą sugestię jako ulepszenie języka Java:
(A może moglibyśmy zacząć oddzielać się od Javy)
źródło
Jeśli raz utworzysz łańcuch. Rozważy to, że jest to obiekt, jeśli chcesz to zmodyfikować, nie jest to możliwe, utworzy nowy obiekt.
źródło