Je ne pouvais pas comprendre la raison de cela. J'utilise toujours la classe String comme les autres développeurs, mais lorsque j'en modifie la valeur, une nouvelle instance de String est créée.
Quelle pourrait être la raison de l'immuabilité de la classe String en Java?
Je sais qu'il existe des alternatives telles que StringBuffer ou StringBuilder. C'est juste de la curiosité.
Réponses:
Simultanéité
Java a été défini dès le départ avec des considérations de simultanéité. Comme souvent mentionné, les mutables partagés sont problématiques. Une chose peut en changer une autre derrière le dos d'un autre thread sans que ce dernier en soit conscient.
Une multitude de bogues C ++ multithreads sont apparus à cause d'une chaîne partagée - un module a pensé qu'il était prudent de le modifier lorsqu'un autre module du code avait enregistré un pointeur sur celui-ci et s'attendait à ce qu'il reste identique.
La «solution» à cela est que chaque classe crée une copie défensive des objets mutables qui lui sont transmis. Pour les chaînes mutables, c’est O (n) pour faire la copie. Pour les chaînes immuables, faire une copie est O (1) car ce n'est pas une copie, c'est le même objet qui ne peut pas changer.
Dans un environnement multithread, les objets immuables peuvent toujours être partagés en toute sécurité. Cela entraîne une réduction globale de l'utilisation de la mémoire et améliore la mise en cache de la mémoire.
Sécurité
Souvent, les chaînes sont transmises comme arguments aux constructeurs - les connexions réseau et les protocoles sont les deux qui viennent le plus facilement à l’esprit. Pouvoir modifier cela à un moment indéterminé plus tard au cours de l'exécution peut poser des problèmes de sécurité (la fonction pensait qu'elle se connectait à une machine, mais qu'elle était déviée vers une autre, mais tout ce qui se trouve dans l'objet a l'air d'être connecté à la première ... c'est même la même chaîne).
Java permet d'utiliser la réflexion - et les paramètres pour cela sont des chaînes. Le danger de passer une chaîne qui peut être modifiée en passant à une autre méthode qui reflète. C'est très mauvais.
Les clés du hachage
La table de hachage est l'une des structures de données les plus utilisées. Les clés de la structure de données sont très souvent des chaînes. Avoir des chaînes immuables signifie que (comme ci-dessus) la table de hachage n'a pas besoin de faire une copie de la clé de hachage à chaque fois. Si les chaînes étaient mutables et que la table de hachage ne le permettait pas, il serait possible que quelque chose change la clé de hachage à distance.
La façon dont fonctionne l’objet en java, c’est que tout a une clé de hachage (accessible via la méthode hashCode ()). Avoir une chaîne immuable signifie que le hashCode peut être mis en cache. Compte tenu de la fréquence à laquelle les chaînes sont utilisées comme clés d'un hachage, cela améliore considérablement les performances (au lieu de devoir recalculer le code de hachage à chaque fois).
Substrings
Si la chaîne est immuable, le tableau de caractères sous-jacent qui sauvegarde la structure de données est également immuable. Cela permet certaines optimisations sur la
substring
méthode à effectuer (elles ne le sont pas nécessairement - cela introduit également la possibilité de fuites de mémoire).Si tu fais:
La valeur de
bar
est 'mile'. Cependant, les deuxfoo
etbar
peuvent être sauvegardés par le même tableau de caractères, ce qui réduit l'instanciation de plusieurs tableaux de caractères ou le copie, à l'aide de points de début et de fin différents dans la chaîne.L’inconvénient de cela (la fuite de mémoire) est que si l’on avait une chaîne longue de 1k et que l’on prenait la sous-chaîne du premier et du deuxième caractère, il serait également sauvegardé par le tableau de caractères long de 1k. Ce tableau resterait en mémoire même si la chaîne d'origine ayant une valeur de l'ensemble du tableau de caractères était récupérée.
On peut voir cela dans String de JDK 6b14 (le code suivant provient d’une source GPL v2 et est utilisé à titre d’exemple).
Notez que la sous-chaîne utilise le constructeur String au niveau du package qui n'implique aucune copie du tableau et serait beaucoup plus rapide (au détriment de la conservation de certains tableaux de grande taille - sans toutefois dupliquer les tableaux de grande taille).
Notez que le code ci-dessus est pour Java 1.6. La façon dont le constructeur de sous-chaînes est implémenté a été modifiée avec Java 1.7, comme indiqué dans la représentation interne Modifications de chaîne effectuée en Java 1.7.0_06 - le problème de cette fuite de mémoire que j'ai mentionné ci-dessus. Java n'était probablement pas considéré comme un langage avec beaucoup de manipulations de chaînes et l'amélioration des performances d'une sous-chaîne était donc une bonne chose. Maintenant, avec d'énormes documents XML stockés dans des chaînes qui ne sont jamais collectées, cela devient un problème ... et donc la modification consiste à ne
String
pas utiliser le même tableau sous-jacent avec une sous-chaîne, de sorte que le tableau de caractères plus grand puisse être collecté plus rapidement.Ne pas abuser de la pile
On pourrait transmettre la valeur de la chaîne au lieu de la référence à la chaîne immuable pour éviter les problèmes de mutabilité. Cependant, avec des chaînes de grande taille, transmettre ceci sur la pile serait ... abusif pour le système (placer des documents xml entiers sous forme de chaînes sur la pile puis les enlever ou continuer à les transmettre ...).
La possibilité de déduplication
Certes, ce n'était pas une motivation initiale pour laquelle les chaînes devraient être immuables, mais quand on examine la raison pour laquelle les chaînes immuables sont une bonne chose, c'est certainement une chose à considérer.
Quiconque a un peu travaillé avec Strings sait qu’il peut aspirer de la mémoire. Cela est particulièrement vrai lorsque vous effectuez des opérations telles que l'extraction de données à partir de bases de données pendant un certain temps. Plusieurs fois avec ces piqûres, ils sont la même chaîne encore et encore (une fois pour chaque ligne).
JEP 192 (motivation citée ci-dessus) est en cours de mise en œuvre avec Java 8 mise à jour 20 pour résoudre ce problème. Sans entrer dans les détails du fonctionnement de la déduplication des chaînes, il est essentiel que les chaînes elles-mêmes soient immuables. Vous ne pouvez pas dédupliquer StringBuilders car ils peuvent changer et vous ne voulez pas que quelqu'un change quelque chose sous vous. Chaînes immuables (liées à ce pool de chaînes) signifie que vous pouvez passer et si vous trouvez deux chaînes identiques, vous pouvez pointer une référence de chaîne vers une autre et laisser le garbage collector utiliser la nouvelle.
Autres langues
L’objectif C (qui est antérieur à Java) a
NSString
etNSMutableString
.C # et .NET ont fait les mêmes choix de conception, la chaîne par défaut étant immuable.
Les cordes Lua sont également immuables.
Python aussi.
Historiquement, Lisp, Scheme, Smalltalk intègrent la chaîne et la rendent immuable. Les langages dynamiques plus modernes utilisent souvent des chaînes d'une manière qui les oblige à être immuables (ce n'est peut-être pas une chaîne , mais c'est immuable).
Conclusion
Ces considérations de conception ont été répétées dans une multitude de langues. Il est généralement admis que les chaînes immuables, malgré leur maladresse, sont meilleures que les alternatives et conduisent à un meilleur code (moins de bugs) et à des exécutables plus rapides.
la source
Raisons dont je peux me souvenir:
Un pool de chaînes sans rendre la chaîne immuable n'est pas du tout possible, car dans le cas du pool de chaînes, un objet / littéral de chaîne, par exemple "XYZ", sera référencé par de nombreuses variables de référence. Ainsi, si l'une d'elles change, la valeur des autres sera automatiquement affectée. .
La chaîne a été largement utilisée comme paramètre pour de nombreuses classes java, par exemple pour ouvrir une connexion réseau, pour ouvrir une connexion de base de données, ouvrir des fichiers. Si String n'est pas immuable, cela entraînerait une grave menace pour la sécurité.
Immutability permet à String de mettre en cache son hashcode.
Le rend thread-safe.
la source
1) Pool Pool
Le concepteur Java sait que String sera le type de données le plus utilisé dans toutes sortes d’applications Java et c’est pourquoi il a voulu optimiser dès le début. L’une des principales étapes dans cette direction était l’idée de stocker les littéraux de chaîne dans le pool de chaînes. L'objectif était de réduire les objets String temporaires en les partageant et pour pouvoir les partager, ils devaient obligatoirement appartenir à la classe Immutable. Vous ne pouvez pas partager un objet mutable avec deux parties qui se sont inconnues. Prenons un exemple hypothétique, où deux variables de référence pointent sur le même objet String:
Maintenant, si s1 modifie l'objet de "Java" en "C ++", la variable de référence a également reçu la valeur s2 = "C ++", dont elle n'est même pas au courant. En rendant String immuable, ce partage de littéral était possible. En bref, l'idée clé du pool de chaînes ne peut pas être mise en œuvre sans rendre String final ou immuable en Java.
2) la sécurité
Java a clairement pour objectif de fournir un environnement sécurisé à tous les niveaux de service et String est essentiel pour tout ce qui concerne la sécurité. String a été largement utilisé comme paramètre pour de nombreuses classes Java. Par exemple, pour ouvrir une connexion réseau, vous pouvez passer l’hôte et le port sous la forme String, pour lire les fichiers en Java, vous pouvez indiquer le chemin des fichiers et le répertoire sous forme de String et pour ouvrir la connexion à la base de données. transmettre l'URL de la base de données sous forme de chaîne. Si String n'était pas immuable, un utilisateur aurait peut-être autorisé l'accès à un fichier particulier dans le système, mais après s'être authentifié, il peut remplacer le chemin PATH par un autre, ce qui pourrait poser de graves problèmes de sécurité. De même, lors de la connexion à une base de données ou à une autre machine du réseau, la mutation de la valeur de String peut constituer une menace pour la sécurité. Les chaînes mutables peuvent également causer des problèmes de sécurité dans Reflection.
3) Utilisation de la ficelle dans le mécanisme de chargement de classe
Une autre raison pour laquelle String final ou Immutable était motivé par le fait qu’elle était fortement utilisée dans le mécanisme de chargement de classe. String n'étant pas immuable, un attaquant peut profiter de ce fait et une demande de chargement de classes Java standard, par exemple java.io.Reader, peut être remplacée par la classe illicite com.unknown.DataStolenReader. En gardant String final et immuable, nous pouvons au moins nous assurer que JVM charge les classes correctes.
4) Avantages du multithreading
Comme la clé de voûte de Java était l’offre simultanée et le multi-threading, il était logique de penser à la sécurité des threads des objets String. Comme il était prévu que String sera utilisé largement, le fait de le rendre immuable signifie qu'aucune synchronisation externe ne signifie, un code beaucoup plus propre impliquant le partage de String entre plusieurs threads. Cette caractéristique unique facilite beaucoup le codage des accès concurrents déjà compliqué, déroutant et sujet aux erreurs. Comme String est immuable et que nous le partageons simplement entre les threads, le code obtenu est plus lisible.
5) Optimisation et performance
Maintenant, quand vous faites une classe Immutable, vous savez à l'avance que cette classe ne va pas changer une fois créée. Cela garantit un chemin ouvert pour de nombreuses optimisations de performances, par exemple la mise en cache. String lui-même sait que, je ne vais pas changer, donc String cache son hashcode. Il calcule même le hashcode lentement et une fois créé, cachez-le simplement. Dans un monde simple, lorsque vous appelez pour la première fois la méthode hashCode () de n'importe quel objet String, elle calcule le code de hachage et tout appel ultérieur à hashCode () renvoie une valeur déjà calculée et mise en cache. Cela se traduit par un bon gain de performances, étant donné que String est fortement utilisé dans les cartes basées sur le hachage, par exemple Hashtable et HashMap. La mise en cache de hashcode n’était pas possible sans la rendre immuable et définitive, car elle dépend du contenu de String lui-même.
la source
La machine virtuelle Java effectue plusieurs optimisations concernant les opérations sur les chaînes qui ne pourraient pas être effectuées autrement. Par exemple, si vous avez une chaîne avec la valeur "Mississippi" et que vous avez affecté "Mississippi" .substring (0, 4) à une autre chaîne, une copie a été faite à partir des quatre premiers caractères pour créer "Miss". . Ce que vous ne savez pas, c'est que les deux partagent la même chaîne d'origine "Mississippi", l'un étant le propriétaire et l'autre une référence de cette chaîne de la position 0 à 4. (La référence au propriétaire empêche le propriétaire de le récupérer par le ramasse-miettes lorsque le propriétaire sort de la portée)
Ceci est trivial pour une chaîne aussi petite que "Mississippi", mais avec des chaînes plus grandes et des opérations multiples, ne pas avoir à copier la chaîne est un gain de temps considérable! Si les chaînes étaient mutables, vous ne pourriez pas le faire, car la modification de l'original affecterait également les "copies" de la sous-chaîne.
En outre, comme Donal le mentionne, l’avantage serait grandement alourdi par son inconvénient. Imaginons que vous écriviez un programme qui dépend d’une bibliothèque et que vous utilisiez une fonction qui renvoie une chaîne. Comment pouvez-vous être sûr que cette valeur restera constante? Pour s'assurer que cela ne se produise pas, vous devez toujours en produire une copie.
Que faire si vous avez deux threads partageant la même chaîne? Vous ne voudriez pas lire une chaîne en cours de réécriture par un autre thread, n'est-ce pas? String devrait donc être thread-safe, ce qui serait la classe la plus courante, ce qui ralentirait pratiquement tous les programmes Java. Sinon, vous devrez faire une copie pour chaque thread qui requiert cette chaîne ou vous devrez placer le code utilisant cette chaîne dans un bloc de synchronisation, ce qui ne fera que ralentir votre programme.
Pour toutes ces raisons, cela a été l'une des premières décisions prises pour Java afin de se différencier du C ++.
la source
La raison de l'immuabilité de string provient de la cohérence avec d'autres types primitifs du langage. Si vous avez un
int
contenant la valeur 42 et que vous y ajoutez la valeur 1, vous ne modifiez pas la 42. Vous obtenez une nouvelle valeur, 43, qui n'a aucun lien avec les valeurs de départ. La mutation de primitives autres que la chaîne n'a aucun sens conceptuel; et en tant que tels programmes qui traitent les chaînes comme immuables sont souvent plus faciles à raisonner et à comprendre.De plus, Java fournit réellement des chaînes mutables et immuables, comme vous le voyez avec
StringBuilder
; vraiment, seule la valeur par défaut est la chaîne immuable. Si vous souhaitez faire référence à deStringBuilder
nombreuses sources, vous êtes parfaitement invité à le faire. Java utilise des types séparés (String
etStringBuilder
) pour ces concepts car il ne prend pas en charge l'expression de la mutabilité ou de l'absence de mutabilité dans son système de types. Dans les langages qui prennent en charge l'immuabilité dans leurs systèmes de types (par exemple, C ++const
), il existe souvent un seul type de chaîne qui sert les deux objectifs.Oui, le fait que string soit immuable permet d'implémenter certaines optimisations spécifiques aux chaînes immuables, telles que l'interning, et permet de passer des références de chaîne sans synchronisation entre les threads. Toutefois, cela confond le mécanisme avec l'objectif visé d'un langage avec un système de types simple et cohérent. Je compare cela à la façon dont tout le monde pense à la collecte des ordures dans le mauvais sens; le ramassage des ordures n'est pas une "récupération de la mémoire inutilisée"; c'est "simuler un ordinateur avec une mémoire illimitée" . Les optimisations de performances discutées sont des choses qui sont faites pour que l’objectif de chaînes immuables fonctionne bien sur de vraies machines; pas la raison pour laquelle de telles chaînes sont immuables en premier lieu.
la source
43 = 6
et vous vous attendez à ce que le chiffre 43 ait le même sens que le numéro 6.i
, pas 42. Envisagezstring s = "Hello "; s += "World";
. Vous avez muté la valeur de variables
. Mais les chaînes"Hello "
,"World"
et"Hello World"
sont immuables.L'immuabilité signifie que les constantes détenues par des classes que vous ne possédez pas ne peuvent pas être modifiées. Les classes que vous ne possédez pas incluent celles qui sont au cœur de l’implémentation de Java, et les chaînes qui ne devraient pas être modifiées incluent des éléments tels que des jetons de sécurité, des adresses de service, etc. Vous ne devriez vraiment pas pouvoir modifier ces tris. des choses (et cela s’applique doublement en mode sandbox).
Si String n'était pas immuable, chaque fois que vous le récupériez dans un contexte ne voulant pas que le contenu de la chaîne soit modifié, vous devez en prendre une copie «au cas où». Cela devient très cher.
la source
String
. Mais, par exemple,Array
s sont néanmoins mutables. Alors, pourquoi sont-ilsString
immuables etArray
non pas. Et si l’immuabilité est si importante, alors pourquoi Java rend-il la création et le travail avec des objets immuables si difficiles?Imaginez un système dans lequel vous acceptez certaines données, vérifiez leur exactitude, puis transmettez-les (pour les stocker dans une base de données, par exemple).
En supposant que les données soient a
String
et qu’elles doivent contenir au moins 5 caractères. Votre méthode ressemble à ceci:Nous pouvons maintenant convenir que, lorsque l’
storeInDatabase
appel est appelé ici, ilinput
répondra aux besoins. Mais si ellesString
étaient mutables, l' appelant pourrait modifier l'input
objet (à partir d'un autre thread) juste après qu'il ait été vérifié et avant qu'il ne soit stocké dans la base de données . Cela nécessiterait un bon timing et ne se déroulerait probablement pas bien à chaque fois, mais il pourrait parfois vous demander de stocker des valeurs non valides dans la base de données.Les types de données immuables constituent une solution très simple à ce problème (et à de nombreux autres problèmes connexes): chaque fois que vous vérifiez une valeur, vous pouvez compter sur le fait que la condition vérifiée est toujours vraie plus tard.
la source
input
de lahandle
méthode est déjà trop longue (peu importe l' origineinput
est), il serait tout simplement jeter une exception. Vous créez une nouvelle entrée avant d' appeler la méthode. Ce n'est pas un problème.En général, vous rencontrerez des types de valeur et des types de référence . Avec un type de valeur, vous ne vous souciez pas de l'objet qui le représente, vous vous souciez de la valeur. Si je vous attribue une valeur, vous vous attendez à ce que cette valeur reste la même. Vous ne voulez pas que ça change soudainement. Le nombre 5 est une valeur. Vous ne vous attendez pas à ce qu'il passe à 6 soudainement. La chaîne "Bonjour" est une valeur. Vous ne vous attendez pas à ce qu'il devienne soudainement "P *** off".
Avec les types de référence, vous vous souciez de l'objet et vous vous attendez à ce qu'il change. Par exemple, vous vous attendez souvent à ce qu'un tableau change. Si je vous donne un tableau et que vous voulez le conserver tel quel, vous devez soit me faire confiance pour ne pas le changer, soit vous en faire une copie.
Avec la classe de chaînes Java, les concepteurs devaient prendre une décision: est-il préférable que les chaînes se comportent comme un type de valeur ou doivent-elles se comporter comme un type de référence? Dans le cas des chaînes Java, il a été décidé qu'elles devraient être des types valeur, ce qui signifie que, comme il s'agit d'objets, il doit s'agir d'objets immuables.
La décision opposée aurait pu être prise, mais aurait, à mon avis, causé beaucoup de maux de tête. Comme indiqué ailleurs, de nombreuses langues ont pris la même décision et sont parvenues à la même conclusion. Une exception est C ++, qui a une classe de chaîne, et les chaînes peuvent être constantes ou non constantes, mais en C ++, contrairement à Java, les paramètres d'objet peuvent être transmis en tant que valeurs et non en tant que références.
la source
Je suis vraiment surpris que personne ne l'a signalé.
Réponse: Cela ne vous apporterait aucun avantage significatif, même s'il était modifiable. Cela ne vous profiterait pas autant que cela cause des problèmes supplémentaires. Examinons les deux cas de mutation les plus courants:
Changer un caractère d'une chaîne
Puisque chaque caractère d'une chaîne Java prend 2 ou 4 octets, demandez-vous, pourriez-vous gagner quelque chose si vous pouviez transformer la copie existante?
Dans le cas où vous remplacez un caractère de 2 octets par un caractère de 4 octets (ou inversement), vous devez décaler le reste de la chaîne de 2 octets à gauche ou à droite. Ce qui n’est pas très différent de copier la chaîne entière du point de vue informatique.
C'est aussi un comportement vraiment irrégulier qui est généralement indésirable. Imaginez que quelqu'un teste une application avec du texte anglais et que l'application soit adoptée par des pays étrangers, tels que la Chine, tout commence à produire des résultats étranges.
Ajouter une autre chaîne (ou caractère) à celle existante
Si vous avez deux chaînes arbitraires, celles-ci se trouvent à deux emplacements de mémoire distincts. Si vous souhaitez modifier le premier en ajoutant le second, vous ne pouvez pas simplement demander de la mémoire supplémentaire à la fin de la première chaîne, car celle-ci est probablement déjà occupée.
Vous devez copier la chaîne concaténée dans un tout nouvel emplacement, exactement comme si les deux chaînes étaient immuables.
Si vous voulez faire des ajouts efficacement, vous pouvez utiliser
StringBuilder
, ce qui réserve un assez gros espace à la fin d'une chaîne, rien que dans le but d'un éventuel ajout ultérieur.la source
ils sont chers et leur maintien immuable permet, par exemple, de partager des sous-chaînes partageant le tableau d'octets de la chaîne principale. (Augmentation de la vitesse également car vous n'avez pas besoin de créer un nouveau tableau d'octets et de le copier)
sécurité - ne voudrait pas que votre code de package ou de classe soit renommé
[Supprimé ancien 3 regardé StringBuilder src - il ne partage pas la mémoire avec une chaîne (jusqu'à ce que modifié) Je pense que c'était en 1.3 ou 1.4]
cache hashcode
pour les chaînes mutalbles, utilisez SB (générateur ou tampon au besoin)
la source
Les chaînes auraient dû être un type de données primitif en Java. S'ils l'avaient été, les chaînes seraient mutables par défaut et le mot-clé final générerait des chaînes immuables. Les chaînes mutables sont utiles et il existe donc plusieurs hacks pour les chaînes mutables dans les classes stringbuffer, stringbuilder et charsequence.
la source