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:
String foo = "smiles";
String bar = foo.substring(1,5);
La valeur de bar
est 'mile'. Cependant, les deux foo
et bar
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.
foo | | (0, 6)
vv
sourit
^ ^
bar | | (1, 5)
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).
public String(char value[], int offset, int count) {
if (offset < 0) {
throw new StringIndexOutOfBoundsException(offset);
}
if (count < 0) {
throw new StringIndexOutOfBoundsException(count);
}
// Note: offset or count might be near -1>>>1.
if (offset > value.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
this.offset = 0;
this.count = count;
this.value = Arrays.copyOfRange(value, offset, offset+count);
}
// Package private constructor which shares value array for speed.
String(int offset, int count, char value[]) {
this.value = value;
this.offset = offset;
this.count = count;
}
public String substring(int beginIndex, int endIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
if (endIndex > count) {
throw new StringIndexOutOfBoundsException(endIndex);
}
if (beginIndex > endIndex) {
throw new StringIndexOutOfBoundsException(endIndex - beginIndex);
}
return ((beginIndex == 0) && (endIndex == count)) ? this :
new String(offset + beginIndex, endIndex - beginIndex, value);
}
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).
De nombreuses applications Java à grande échelle sont actuellement saturées en mémoire. Les mesures ont montré qu'environ 25% du jeu de données en temps réel Java dans ces types d'applications sont consommés par des objets String. En outre, environ la moitié de ces objets String sont des doublons, doublons signifiant que string1.equals (string2) est true. Le fait de dupliquer des objets String sur le tas est essentiellement un gaspillage de mémoire. ...
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
et NSMutableString
.
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.