Contexte
Il y a trois endroits où final peut apparaître en Java:
Faire une finale de classe empêche tout sous-classement de la classe. La finalisation d'une méthode empêche les sous-classes de la méthode de la remplacer. Rendre un champ final empêche qu'il ne soit modifié ultérieurement.
Idées fausses
Des optimisations se produisent autour des méthodes et des champs finaux.
Une dernière méthode facilite l'optimisation de HotSpot via l'inlining. Cependant, HotSpot le fait même si la méthode n'est pas définitive car elle fonctionne en supposant qu'elle n'a pas été remplacée jusqu'à preuve du contraire. En savoir plus sur SO
Une variable finale peut être optimisée de manière agressive, et plus à ce sujet peut être lue dans la section JLS 17.5.3 .
Cependant, avec cette compréhension, il faut savoir qu'aucune de ces optimisations ne concerne la finalisation d'une classe . Il n'y a aucun gain de performance en faisant une finale de classe.
L'aspect final d'une classe n'a rien à voir non plus avec l'immuabilité. On peut avoir une classe immuable (telle que BigInteger ) qui n'est pas finale, ou une classe qui est mutable et finale (telle que StringBuilder ). La décision de savoir si une classe doit être finale est une question de conception.
Conception finale
Les chaînes sont l'un des types de données les plus utilisés. Ils se trouvent comme clés de cartes, ils stockent des noms d'utilisateur et des mots de passe, ils sont ce que vous lisez à partir d'un clavier ou d'un champ sur une page Web. Les cordes sont partout .
Plans
La première chose à considérer de ce qui se passerait si vous pouviez sous-classer String est de réaliser que quelqu'un pourrait construire une classe String mutable qui semblerait autrement être une String. Cela gâcherait Maps partout.
Considérez ce code hypothétique:
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");
C'est un problème avec l'utilisation d'une clé mutable en général avec une carte, mais ce que j'essaie de comprendre, c'est que soudainement un certain nombre de choses à propos de la rupture de la carte. L'entrée n'est plus au bon endroit sur la carte. Dans un HashMap, la valeur de hachage a (aurait dû) changé et donc ce n'est plus à la bonne entrée. Dans TreeMap, l'arborescence est maintenant cassée car l'un des nœuds est du mauvais côté.
Étant donné que l'utilisation d'une chaîne pour ces clés est si courante, ce comportement doit être évité en rendant la chaîne finale.
Vous pourriez être intéressé à lire Pourquoi String est-il immuable en Java? pour en savoir plus sur la nature immuable de Strings.
Cordes néfastes
Il existe un certain nombre d'options néfastes pour les chaînes. Considérez si j'ai fait une chaîne qui retournait toujours vrai quand égal était appelé ... et passé cela dans une vérification de mot de passe? Ou fait en sorte que les affectations à MyString envoient une copie de la chaîne à une adresse e-mail?
Ce sont des possibilités très réelles lorsque vous avez la possibilité de sous-classer String.
Optimisations de chaîne Java.lang
Alors qu'avant, j'ai mentionné que la finale ne rend pas la chaîne plus rapide. Cependant, la classe String (et les autres classes java.lang
) utilise fréquemment la protection au niveau du package des champs et des méthodes pour permettre aux autres java.lang
classes de pouvoir bricoler avec les internes plutôt que de passer par l'API publique pour String tout le temps. Fonctions comme getChars sans vérification de plage ou lastIndexOf qui est utilisé par StringBuffer ou le constructeur qui partage le tableau sous-jacent (notez que c'est une chose Java 6 qui a été modifiée en raison de problèmes de mémoire).
Si quelqu'un créait une sous-classe de String, il ne serait pas en mesure de partager ces optimisations (sauf si cela en faisait partie java.lang
aussi, mais c'est un package scellé ).
Il est plus difficile de concevoir quelque chose pour l'extension
Concevoir quelque chose pour être extensible est difficile . Cela signifie que vous devez exposer des parties de vos composants internes pour que quelque chose d'autre puisse être modifié.
Une fuite de mémoire ne pouvait pas avoir sa fuite de mémoire fixée. Ces parties devraient avoir été exposées à des sous-classes et changer ce code signifierait alors que les sous-classes se briseraient.
Java se targue de la compatibilité descendante et en ouvrant les classes de base à l'extension, on perd une partie de cette capacité à réparer les choses tout en conservant la calculabilité avec les sous-classes tierces.
Checkstyle a une règle qu'il applique (qui me frustre vraiment lors de l'écriture de code interne) appelée "DesignForExtension" qui impose que chaque classe soit:
- Abstrait
- Final
- Implémentation vide
Le rationnel pour lequel est:
Ce style de conception d'API protège les superclasses contre les cassures de sous-classes. L'inconvénient est que les sous-classes sont limitées dans leur flexibilité, en particulier, elles ne peuvent pas empêcher l'exécution de code dans la superclasse, mais cela signifie également que les sous-classes ne peuvent pas corrompre l'état de la superclasse en oubliant d'appeler la méthode super.
Autoriser l'extension des classes d'implémentation signifie que les sous-classes peuvent éventuellement corrompre l'état de la classe à partir de laquelle elles sont basées et faire en sorte que diverses garanties que la superclasse donne ne soient pas valides. Pour quelque chose d' aussi complexe que la chaîne, il est presque certain que le changement de partie se casser quelque chose.
Développeur hurbis
Sa part d'être développeur. Considérez la probabilité que chaque développeur crée sa propre sous-classe String avec sa propre collection d' utilitaires . Mais maintenant, ces sous-classes ne peuvent pas être librement attribuées les unes aux autres.
WleaoString foo = new WleaoString("foo");
MichaelTString bar = foo; // This doesn't work.
Cette voie mène à la folie. Casting à String partout et vérifier si la String est une instance de votre classe String ou si non, créer une nouvelle String basée sur celle-ci et ... juste, non. Non.
Je suis sûr que vous pouvez écrire une bonne classe String ... mais laisser l' écriture des implémentations de chaînes multiples à ces fous qui écrivent C ++ et doivent traiter std::string
et char*
et quelque chose de stimuler et sChaîne , et tout le reste .
Java String Magic
Il y a plusieurs choses magiques que Java fait avec les chaînes. Ceux-ci facilitent la tâche d'un programmeur mais introduisent certaines incohérences dans le langage. Autoriser des sous-classes sur String nécessiterait une réflexion très importante sur la façon de gérer ces choses magiques:
Littéraux de chaîne (JLS 3.10.5 )
Avoir du code qui permet de faire:
String foo = "foo";
Cela ne doit pas être confondu avec la boxe des types numériques comme Integer. Vous ne pouvez pas le faire 1.toString()
, mais vous pouvez le faire "foo".concat(bar)
.
L' +
opérateur (JLS 15.18.1 )
Aucun autre type de référence en Java ne permet d'utiliser un opérateur dessus. La chaîne est spéciale. L'opérateur de concaténation de chaînes fonctionne également au niveau du compilateur de sorte que cela "foo" + "bar"
se produit "foobar"
lors de la compilation plutôt qu'au moment de l'exécution.
Conversion de chaînes (JLS 5.1.11 )
Tous les objets peuvent être convertis en chaînes simplement en les utilisant dans un contexte de chaîne.
Interning de chaînes ( JavaDoc )
La classe String a accès au pool de chaînes qui lui permet d'avoir des représentations canoniques de l'objet qui est rempli au type de compilation avec les littéraux String.
Autoriser une sous-classe de chaîne signifierait que ces bits avec la chaîne qui facilitent la programmation deviendraient très difficiles ou impossibles à faire lorsque d'autres types de chaînes sont possibles.