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:
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");
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 andere java.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:
- Abstrakt
- Finale
- Leere Implementierung
Der Grund dafür ist:
Dieser API-Entwurfsstil schützt Superklassen vor dem Aufbrechen durch Unterklassen. Der Nachteil ist, dass Unterklassen in ihrer Flexibilität begrenzt sind, insbesondere können sie die Ausführung von Code in der Superklasse nicht verhindern. Dies bedeutet jedoch auch, dass Unterklassen den Status der Superklasse nicht beschädigen können, indem sie den Aufruf der Supermethode vergessen.
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.
WleaoString foo = new WleaoString("foo");
MichaelTString bar = foo; // This doesn't work.
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
und char*
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:
String foo = "foo";
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.