Ich bin ein langjähriger Java-Entwickler und habe nach dem Hauptfach Zeit, es anständig zu studieren, um die Zertifizierungsprüfung abzulegen. Eine Sache, die mich immer gestört hat, ist, dass String "endgültig" ist. Ich verstehe es, wenn ich etwas über Sicherheitsprobleme und ähnliches lese ... Aber im Ernst, hat jemand ein echtes Beispiel dafür?
Was würde zum Beispiel passieren, wenn String nicht final wäre? Als wäre es nicht in Ruby. Ich habe keine Beschwerden von der Ruby-Community gehört ... Und mir sind die StringUtils und verwandten Klassen bekannt, die Sie entweder selbst implementieren oder über das Web suchen müssen, um nur dieses Verhalten (4 Codezeilen) zu implementieren sind dazu bereit.
java.io.File
nichtfinal
. Oh, das ist es nicht. Bugger.Antworten:
Der Hauptgrund liegt in der Geschwindigkeit:
final
Klassen können nicht erweitert werden, wodurch die JIT alle Arten von Optimierungen beim Umgang mit Zeichenfolgen durchführen kann - es ist nie erforderlich, nach überschriebenen Methoden zu suchen.Ein weiterer Grund ist die Thread-Sicherheit: Unveränderliche Dateien sind immer thread-sicher, da ein Thread sie vollständig erstellen muss, bevor sie an eine andere Person übergeben werden können - und nach dem Erstellen können sie nicht mehr geändert werden.
Auch die Erfinder der Java-Laufzeit wollten immer auf der Seite der Sicherheit irren. Wenn Sie String erweitern können (was ich in Groovy oft mache, weil es so praktisch ist), können Sie eine ganze Dose Würmer öffnen, wenn Sie nicht wissen, was Sie tun.
quelle
final
zur schnellen Überprüfung, ob eine Methode beim Kompilieren von Code nicht überschrieben wird.final
Schlüsselwort in der Klasse. Das darin enthaltene Zeichenarray ist final und daher unveränderlich. Wenn Sie die Klasse selbst final machen, wird die Schleife geschlossen, wenn @abl erwähnt wird, da eine veränderbare Unterklasse nicht eingeführt werden kann.Es gibt einen weiteren Grund, warum Javas
String
Klasse endgültig sein muss: In einigen Szenarien ist dies für die Sicherheit wichtig. Wenn dieString
Klasse nicht endgültig wäre, wäre der folgende Code für subtile Angriffe eines böswilligen Aufrufers anfällig:Der Angriff: Ein böswilliger Aufrufer könnte eine Unterklasse von String erstellen
EvilString
, in derEvilString.startsWith()
immer true zurückgegeben wird, der Wert vonEvilString
jedoch böse ist (zjavascript:alert('xss')
. B. ). Aufgrund der Unterklasse würde dies der Sicherheitsüberprüfung entgehen. Dieser Angriff wird als TOCTTOU-Sicherheitsanfälligkeit (Time-of-Check-to-Time-of-Use) bezeichnet: zwischen dem Zeitpunkt, zu dem die Überprüfung durchgeführt wurde (die URL beginnt mit http / https) und dem Zeitpunkt, zu dem der Wert verwendet wird (um das HTML-Snippet zu konstruieren), kann sich der effektive Wert ändern. Wenn diesString
nicht endgültig wäre, wären die Risiken von TOCTTOU allgegenwärtig.Wenn dies
String
nicht der Fall ist, wird es schwierig, sicheren Code zu schreiben, wenn Sie Ihrem Anrufer nicht vertrauen können. Dies ist natürlich genau die Position, in der sich die Java-Bibliotheken befinden: Sie werden möglicherweise von nicht vertrauenswürdigen Applets aufgerufen, sodass sie es nicht wagen, ihrem Aufrufer zu vertrauen. Dies bedeutet, dass es unangemessen schwierig wäre, sicheren Bibliothekscode zu schreiben, wenn erString
nicht endgültig wäre.quelle
char[]
Innere der Saite zu modifizieren , aber es erfordert etwas Glück beim Timing.char[]
in der Zeichenfolge zu ändern . Daher können sie die TOCTTOU-Sicherheitsanfälligkeit nicht ausnutzen.String
nicht endgültig wäre. Solange dieses Bedrohungsmodell relevant ist, ist es wichtig, sich dagegen zu verteidigen. Solange es für Applets und anderen nicht vertrauenswürdigen Code wichtig ist, ist dies wahrscheinlich genug, um dieString
endgültige Erstellung zu rechtfertigen , auch wenn es für vertrauenswürdige Anwendungen nicht benötigt wird. (In jedem Fall können vertrauenswürdige Anwendungen bereits alle Sicherheitsmaßnahmen umgehen, unabhängig davon, ob sie endgültig sind.)Wenn nicht, wären Multithread-Java-Apps ein Durcheinander (noch schlimmer als das, was tatsächlich ist).
IMHO Der Hauptvorteil von finalen Strings (unveränderlich) ist, dass sie inhärent threadsicher sind: Sie erfordern keine Synchronisation (das Schreiben dieses Codes ist manchmal ziemlich trivial, aber mehr als weniger weit davon entfernt). Wenn die veränderlich wären, wäre es sehr schwierig, Dinge wie Multithread-Windows-Toolkits zu gewährleisten, und diese Dinge sind unbedingt erforderlich.
quelle
final
Klassen haben nichts mit der Wandlungsfähigkeit der Klasse zu tun.Ein weiterer Vorteil
String
der Finalität besteht darin, dass vorhersehbare Ergebnisse wie Unveränderlichkeit gewährleistet sind.String
Obwohl es sich um eine Klasse handelt, soll sie in einigen Szenarien wie ein Werttyp behandelt werden (der Compiler unterstützt sie sogar überString blah = "test"
). Wenn Sie also eine Zeichenfolge mit einem bestimmten Wert erstellen, erwarten Sie, dass diese ein bestimmtes Verhalten aufweist, das an eine beliebige Zeichenfolge gebunden ist, ähnlich den Erwartungen, die Sie an Ganzzahlen haben würden.Denken Sie darüber nach: Was ist, wenn Sie
java.lang.String
dieequals
oderhashCode
-Methoden unterklassifizieren und überschreiben ? Plötzlich konnten sich zwei Strings "test" nicht mehr gleichen.Stellen Sie sich das Chaos vor, wenn ich das könnte:
eine andere Klasse:
quelle
Hintergrund
Es gibt drei Stellen, an denen final in Java angezeigt werden kann:
Das Finalisieren einer Klasse verhindert alle Unterklassen der Klasse. Das Finalisieren einer Methode verhindert, dass Unterklassen der Methode diese überschreiben. Das Finalisieren eines Feldes verhindert, dass es später geändert wird.
Missverständnisse
Es gibt Optimierungen, die sich auf endgültige Methoden und Felder beziehen.
Eine abschließende Methode erleichtert HotSpot die Optimierung über Inlining. HotSpot führt dies jedoch aus, auch wenn die Methode nicht endgültig ist, da davon ausgegangen wird, dass sie bis zum Beweis des Gegenteils nicht überschrieben wurde. Mehr dazu auf SO
Eine endgültige Variable kann aggressiv optimiert werden, und mehr dazu im JLS-Abschnitt 17.5.3 .
Mit diesem Verständnis sollte man sich jedoch darüber im Klaren sein, dass es bei keiner dieser Optimierungen darum geht, eine Klasse endgültig zu machen. Es gibt keinen Leistungsgewinn, wenn eine Klasse zum Finale wird.
Der letzte Aspekt einer Klasse hat auch nichts mit Unveränderlichkeit zu tun. Man kann eine unveränderliche Klasse (wie BigInteger ) haben, die nicht final ist, oder eine Klasse, die veränderlich und final ist (wie StringBuilder ). Die Entscheidung, ob eine Klasse endgültig sein soll, ist eine Frage des Designs.
Endgültiges Design
Zeichenfolgen gehören zu den am häufigsten verwendeten Datentypen. Sie werden als Schlüssel für Karten gefunden, sie speichern Benutzernamen und Kennwörter, sie sind das, was Sie über eine Tastatur oder ein Feld auf einer Webseite einlesen. Saiten sind überall .
Karten
Als Erstes müssen Sie berücksichtigen, was passieren würde, wenn Sie eine Unterklasse für String erstellen könnten, indem Sie erkennen, dass jemand eine veränderbare String-Klasse erstellen könnte, die ansonsten anscheinend ein String wäre. Dies würde Maps überall durcheinander bringen.
Betrachten Sie diesen hypothetischen Code:
Dies ist ein Problem bei der Verwendung eines veränderlichen Schlüssels im Allgemeinen mit einer Karte, aber die Sache, die ich versuche, dort anzukommen, ist, dass plötzlich eine Reihe von Dingen an der Karte kaputt gehen. Der Eintrag befindet sich nicht mehr an der richtigen Stelle in der Karte. In einer HashMap hat sich der Hash-Wert geändert (sollte sich geändert haben) und ist daher nicht mehr am richtigen Eintrag. In der TreeMap ist der Baum nun gebrochen, weil sich einer der Knoten auf der falschen Seite befindet.
Da die Verwendung eines Strings für diese Schlüssel so verbreitet ist, sollte dieses Verhalten verhindert werden, indem der String als final definiert wird.
Vielleicht interessiert Sie das Lesen Warum ist String in Java unveränderlich? Hier erfahren Sie mehr über die Unveränderlichkeit von Strings.
Schändliche Saiten
Es gibt eine Reihe von verschiedenen schändlichen Optionen für Strings. Überlegen Sie, ob ich einen String erstellt habe, der beim Aufruf von equal immer true zurückgibt ... und diesen an eine Kennwortprüfung weiterleitet? Oder wurde es so gemacht, dass Zuweisungen an MyString eine Kopie des Strings an eine E-Mail-Adresse senden würden?
Dies sind sehr reale Möglichkeiten, wenn Sie die Möglichkeit haben, String in Unterklassen zu unterteilen.
Java.lang String-Optimierungen
Während ich vorhin erwähnte, macht das Finale String nicht schneller. Die String-Klasse (und andere Klassen in
java.lang
) verwenden jedoch häufig den Schutz von Feldern und Methoden auf Paketebene, damit anderejava.lang
Klassen mit den Interna basteln können, anstatt die öffentliche API für String ständig zu durchlaufen. Funktionen wie getChars ohne Bereichsprüfung oder lastIndexOf , das von StringBuffer verwendet wird, oder der Konstruktor , der das zugrunde liegende Array gemeinsam nutzt (beachten Sie, dass dies ein Java 6-Element ist, das aufgrund von Speicherproblemen geändert wurde).Wenn jemand eine Unterklasse von String erstellt hätte, wäre er nicht in der Lage, diese Optimierungen zu teilen (es sei denn, es war auch Teil von
java.lang
, aber das ist ein versiegeltes Paket ).Es ist schwieriger, etwas für die Erweiterung zu entwerfen
Es ist schwierig, etwas zu entwerfen, das erweiterbar ist . Dies bedeutet, dass Sie Teile Ihrer Interna freigeben müssen, damit etwas anderes geändert werden kann.
Bei einem erweiterbaren String konnte der Speicherverlust nicht behoben werden. Diese Teile müssten Unterklassen ausgesetzt worden sein, und das Ändern dieses Codes würde dann bedeuten, dass die Unterklassen beschädigt würden.
Java ist stolz auf seine Abwärtskompatibilität. Indem man Kernklassen für Erweiterungen öffnet, verliert man die Fähigkeit, Probleme zu beheben, während die Kompatibilität mit Unterklassen von Drittanbietern erhalten bleibt.
Checkstyle hat eine Regel namens "DesignForExtension", die erzwingt (was mich beim Schreiben von internem Code wirklich frustriert), dass jede Klasse entweder:
Der Grund dafür ist:
Das Ermöglichen der Erweiterung von Implementierungsklassen bedeutet, dass die Unterklassen möglicherweise den Status der Klasse, auf der sie basieren, verfälschen und so festlegen können, dass verschiedene Garantien, die die Superklasse gibt, ungültig sind. Für etwas so komplex wie String, ist es fast sicher , dass ein Teil davon zu ändern wird etwas brechen.
Entwickler hurbis
Es gehört dazu, Entwickler zu sein. Berücksichtigen Sie die Wahrscheinlichkeit, dass jeder Entwickler eine eigene String-Unterklasse mit einer eigenen Sammlung von Dienstprogrammen erstellt . Jetzt können diese Unterklassen nicht mehr frei einander zugeordnet werden.
Dieser Weg führt zum Wahnsinn. Überall in String umwandeln und prüfen, ob der String eine Instanz Ihrer String-Klasse ist oder nicht. Dann einen neuen String basierend auf diesem erstellen und ... einfach nein. Nicht.
Ich bin mir sicher, dass Sie eine gute String-Klasse schreiben können ... aber überlassen Sie es diesen verrückten Leuten, die C ++ schreiben und sich mit
std::string
undchar*
und etwas von Boost und SString und dem Rest auseinandersetzen müssen , das Schreiben mehrerer String-Implementierungen .Java String Magie
Es gibt verschiedene magische Dinge, die Java mit Strings macht. Dies erleichtert einem Programmierer den Umgang mit der Sprache, führt jedoch zu einigen Inkonsistenzen in der Sprache. Das Zulassen von Unterklassen in String erfordert einige sehr wichtige Überlegungen zum Umgang mit diesen magischen Dingen:
String-Literale (JLS 3.10.5 )
Mit Code, der Folgendes ermöglicht:
Dies ist nicht zu verwechseln mit Boxing der numerischen Typen wie Integer. Sie können nicht tun
1.toString()
, aber Sie können tun"foo".concat(bar)
.Der
+
Betreiber (JLS 15.18.1 )Kein anderer Referenztyp in Java erlaubt die Verwendung eines Operators. Die Zeichenfolge ist etwas Besonderes. Der String - Verkettungsoperator arbeitet auch an der Compiler - Ebene , so dass
"foo" + "bar"
wird ,"foobar"
wenn es kompiliert wird , anstatt zur Laufzeit.String-Konvertierung (JLS 5.1.11 )
Alle Objekte können in Strings konvertiert werden, indem sie in einem String-Kontext verwendet werden.
String Interning ( JavaDoc )
Die String-Klasse hat Zugriff auf den Pool von Strings, mit dem kanonische Darstellungen des Objekts möglich sind, das beim Kompilierungstyp mit den String-Literalen gefüllt wird.
Das Zulassen einer Unterklasse von String würde bedeuten, dass diese Bits mit dem String, die das Programmieren erleichtern, sehr schwierig oder unmöglich sind, wenn andere String-Typen möglich sind.
quelle
Wenn String nicht endgültig wäre, würde jeder Programmierer-Werkzeugkasten einen eigenen "String mit nur ein paar netten Hilfsmethoden" enthalten. Jede davon wäre mit allen anderen Werkzeugkisten nicht kompatibel.
Ich hielt es für eine dumme Einschränkung. Heute bin ich überzeugt, dass dies eine sehr gute Entscheidung war. Es hält Streicher für Streicher.
quelle
final
in Java ist nicht dasselbe wiefinal
in C #. In diesem Zusammenhang ist es dasselbe wie in C #readonly
. Siehe stackoverflow.com/questions/1327544/…public final class String
.