Question

Pourquoi ont-ils décidé de rendre les chaînes immuables en Java et .NET (et quelques autres langages)? Pourquoi ne l'ont-ils pas rendu mutable?

Était-ce utile?

La solution

Selon Java effectif , chapitre 4, page 73, 2e édition:

  

"Il existe de nombreuses bonnes raisons à cela: les classes immuables sont plus faciles à   concevoir, implémenter et utiliser des classes mutables. Ils sont moins enclins   à l'erreur et sont plus sécurisés.

     

[...]

     

" Les objets immuables sont simples. Un objet immuable peut être en   exactement un état, l'état dans lequel il a été créé. Si tu t'assures   que tous les constructeurs établissent des invariants de classe, alors il est   garantis que ces invariants resteront vrais pour toujours, avec   aucun effort de votre part.

     

[...]

     

Les objets immuables sont intrinsèquement thread-safe; ils ne nécessitent aucune synchronisation. Ils ne peuvent pas être corrompus par plusieurs threads.   y accéder simultanément. C'est de loin l'approche la plus facile   pour atteindre la sécurité du fil. En fait, aucun fil ne peut jamais observer   effet d'un autre thread sur un objet immuable. Donc,   Les objets immuables peuvent être partagés librement

     

[...]

Autres petits points du même chapitre:

  

Vous pouvez non seulement partager des objets immuables, mais également leurs composants internes.

     

[...]

     

Les objets immuables constituent d'excellents éléments de construction pour d'autres objets, qu'ils soient mutables ou immuables.

     

[...]

     

Le seul inconvénient réel des classes immuables est qu’elles nécessitent un objet distinct pour chaque valeur distincte.

Autres conseils

Il y a au moins deux raisons.

Première - sécurité http://www.javafaq.nu/ java-article1060.html

  

La raison principale pour laquelle String fait   immuable était la sécurité. Regarde ça   exemple: nous avons une méthode d'ouverture de fichier   avec vérification de connexion. Nous passons une chaîne à   cette méthode pour traiter l'authentification   ce qui est nécessaire avant l'appel   sera passé à l'OS. Si String était   mutable il était possible en quelque sorte   modifier son contenu après la   vérification de l'authentification avant que le système d'exploitation ne soit disponible   demande du programme alors il est   possible de demander n'importe quel fichier. Donc si   vous avez le droit d'ouvrir un fichier texte dans   répertoire utilisateur, mais à la volée   quand en quelque sorte vous réussissez à changer le   nom du fichier que vous pouvez demander d'ouvrir   " passwd " fichier ou tout autre. Puis un   le fichier peut être modifié et il sera   possible de se connecter directement au système d'exploitation.

Deuxième - Efficacité de la mémoire http://hikrish.blogspot.com/2006/07/why-string-class-is-immutable.html

  

La machine virtuelle Java gère la chaîne " String   Piscine " ;. Pour atteindre la mémoire   efficacité, la machine virtuelle Java fera référence à la chaîne   objet de la piscine. Il ne créera pas   les nouveaux objets String. Alors, à chaque fois   vous créez un nouveau littéral de chaîne, JVM   vérifiera dans la piscine si   existe déjà ou pas. Si déjà   présent dans la piscine, il suffit de donner la   référence au même objet ou créer   le nouvel objet dans la piscine. Il y aura   être nombreuses références pointent vers le même   Objets de chaîne, si quelqu'un change le   valeur, cela affectera tous les   références. Alors, le soleil a décidé de le faire   immuable.

En fait, les chaînes de caractères qui sont immuables en Java n’ont pas grand chose à voir avec la sécurité. Les deux raisons principales sont les suivantes:

Sécurité de tête:

Les chaînes sont un type d'objet extrêmement répandu. Il est donc plus ou moins garanti d’être utilisé dans un environnement multi-thread. Les chaînes sont immuables pour garantir la sécurité du partage de chaînes entre les threads. Avoir des chaînes immuables garantit que lors du passage de chaînes du thread A à un autre thread B, le thread B ne peut pas modifier de manière inattendue la chaîne du thread A.

Cela permet non seulement de simplifier la tâche déjà compliquée de la programmation multithread, mais également d’améliorer les performances des applications multithreads. L'accès aux objets mutables doit être synchronisé de façon ou d'autre quand il est possible d'y accéder à partir de plusieurs threads, afin de s'assurer qu'un thread ne tente pas de lire la valeur de votre objet alors qu'il est modifié par un autre thread. Une synchronisation correcte est difficile à réaliser correctement pour le programmeur et coûteuse au moment de l'exécution. Les objets immuables ne peuvent pas être modifiés et ne nécessitent donc pas de synchronisation.

Performance:

Bien que l’internalisation des chaînes ait été mentionnée, elle ne représente qu’un léger gain d’efficacité en termes de mémoire pour les programmes Java. Seuls les littéraux de chaîne sont internés. Cela signifie que seules les chaînes identiques dans votre code source partageront le même objet String. Si votre programme crée dynamiquement des chaînes identiques, elles seront représentées dans différents objets.

Plus important encore, les chaînes immuables leur permettent de partager leurs données internes. Pour de nombreuses opérations sur les chaînes, cela signifie qu'il n'est pas nécessaire de copier le tableau de caractères sous-jacent. Par exemple, supposons que vous preniez les cinq premiers caractères de String. En Java, vous appelez myString.substring (0,5). Dans ce cas, la méthode substring () consiste simplement à créer un nouvel objet String partageant le caractère sous-jacent de myString [], mais qui sait qu'il commence à l'index 0 et se termine à l'index 5 de ce caractère []. Pour le mettre sous forme graphique, vous obtiendrez ce qui suit:

 |               myString                  |
 v                                         v
"The quick brown fox jumps over the lazy dog"   <-- shared char[]
 ^   ^
 |   |  myString.substring(0,5)

Cela rend ce type d’opérations extrêmement bon marché, et O (1) puisque l’opération ne dépend pas de la longueur de la chaîne originale, ni de la longueur de la sous-chaîne que nous devons extraire. Ce comportement présente également des avantages en termes de mémoire, car de nombreuses chaînes peuvent partager leur caractère sous-jacent [].

Sécurité et performance du fil. Si une chaîne ne peut pas être modifiée, il est sûr et rapide de faire passer une référence entre plusieurs threads. Si les chaînes étaient modifiables, vous devrez toujours copier tous les octets de la chaîne dans une nouvelle instance ou fournir une synchronisation. Une application typique lit une chaîne 100 fois pour chaque fois que cette chaîne doit être modifiée. Voir wikipedia sur la immuabilité .

Il faut vraiment se demander "pourquoi X devrait-il être mutable?" Il est préférable de passer par défaut à l’immuabilité, en raison des avantages déjà mentionnés par Princess Fluff . Il devrait être une exception que quelque chose est mutable.

Malheureusement, la plupart des langages de programmation actuels adoptent la mutabilité, mais nous espérons qu’à l’avenir, la valeur par défaut sera davantage sur l’immuabilité (voir Liste de souhaits pour le prochain langage de programmation grand public ).

Un facteur est que, si les chaînes étaient mutables, les objets stockant des chaînes devraient veiller à stocker les copies, de peur que leurs données internes ne changent sans préavis. Etant donné que les chaînes sont un type assez primitif comme les nombres, il est agréable de pouvoir les traiter comme si elles étaient passées par valeur, même si elles sont passées par référence (ce qui permet également d'économiser de la mémoire).

Wow! Je ne peux pas croire la désinformation ici. Les chaînes étant immuables n'ont rien avec la sécurité. Si quelqu'un a déjà accès aux objets dans une application en cours d'exécution (ce qui devrait être supposé si vous essayez de vous protéger contre le "piratage" d'une chaîne dans votre application), il y aurait certainement beaucoup d'autres possibilités de piratage.

C’est une idée assez nouvelle que l’immuabilité de String résout les problèmes de threading. Hmmm ... J'ai un objet qui est modifié par deux threads différents. Comment résoudre ça? synchroniser l'accès à l'objet? Naawww ... ne laissons personne changer l'objet du tout - cela réglera tous nos problèmes de concurrence d'accès! En fait, rendons tous les objets immuables, puis nous pourrons supprimer le contrat synchonisé du langage Java.

La vraie raison (indiquée par d'autres ci-dessus) est l'optimisation de la mémoire. Dans toute application, il est assez courant d'utiliser le même littéral de façon répétée. En fait, il est si courant que de nombreux compilateurs ont optimisé le stockage d’une seule instance d’un littéral de chaîne il ya plusieurs décennies. L'inconvénient de cette optimisation est que le code d'exécution qui modifie un littéral de chaîne pose un problème car il modifie l'instance pour tous les autres codes qui le partagent. Par exemple, il ne serait pas bon qu'une fonction située quelque part dans une application modifie le littéral de chaîne "chien". pour "chat". Un printf ("chien") aurait pour résultat un "chat" être écrit sur stdout. Pour cette raison, il fallait un moyen de se protéger contre le code qui tente de modifier les littéraux de chaîne (c'est-à-dire, les rendre immuables). Certains compilateurs (avec le support du système d’exploitation) y parviendraient en plaçant le littéral chaîne dans un segment spécial en lecture seule qui provoquerait une erreur de mémoire si une tentative d’écriture était effectuée.

En Java, cela s'appelle interner. Le compilateur Java ne fait que suivre une optimisation de mémoire standard effectuée par les compilateurs depuis des décennies. Et pour résoudre le même problème de modification de ces littéraux de chaîne au moment de l’exécution, Java rend simplement la classe String immuable (c’est-à-dire qu’il ne vous donne aucun paramètre qui vous permettrait de modifier le contenu de la chaîne). Les chaînes ne devraient pas nécessairement être immuables si l'internement des littéraux de chaîne ne se produisait pas.

String n'est pas un type primitif, mais vous souhaitez normalement l'utiliser avec une sémantique de valeur, c'est-à-dire comme une valeur.

Une valeur est une chose en laquelle vous pouvez avoir confiance et qui ne changera pas dans votre dos. Si vous écrivez: String str = someExpr (); Vous ne voulez pas que cela change à moins de faire quelque chose avec str.

La chaîne en tant qu'objet a naturellement une sémantique de pointeur. Pour obtenir la sémantique de valeur, elle doit également être immuable.

Je sais que c'est une bosse, mais ... Sont-ils vraiment immuables? Considérez ce qui suit.

public static unsafe void MutableReplaceIndex(string s, char c, int i)
{
    fixed (char* ptr = s)
    {
        *((char*)(ptr + i)) = c;
    }
}

...

string s = "abc";
MutableReplaceIndex(s, '1', 0);
MutableReplaceIndex(s, '2', 1);
MutableReplaceIndex(s, '3', 2);
Console.WriteLine(s); // Prints 1 2 3

Vous pouvez même en faire une méthode d'extension.

public static class Extensions
{
    public static unsafe void MutableReplaceIndex(this string s, char c, int i)
    {
        fixed (char* ptr = s)
        {
            *((char*)(ptr + i)) = c;
        }
    }
}

Ce qui fait le travail suivant

s.MutableReplaceIndex('1', 0);
s.MutableReplaceIndex('2', 1);
s.MutableReplaceIndex('3', 2);

Conclusion: Ils sont dans un état immuable connu du compilateur. Bien entendu, ce qui précède s’applique uniquement aux chaînes .NET, car Java n’a pas de pointeur. Cependant, une chaîne peut être entièrement mutable à l'aide de pointeurs en C #. Ce n'est pas la façon dont les pointeurs sont destinés à être utilisés, qui ont un usage pratique ou qui sont utilisés en toute sécurité; il est cependant possible, courbant ainsi l'ensemble "mutable" règle. Normalement, vous ne pouvez pas modifier directement l’index d’une chaîne et c’est le seul moyen. Cela pourrait être évité en interdisant les occurrences de pointeur de chaînes ou en effectuant une copie lorsqu'une chaîne est pointée, mais aucune n'est effectuée, ce qui rend les chaînes en C # non pas totalement immuables.

Dans la plupart des cas, une "chaîne". est (utilisé / traité comme / pensé / supposé être) une unité atomique significative, tout comme un nombre .

Demander pourquoi les caractères individuels d'une chaîne de caractères ne sont pas mutables revient à demander pourquoi les bits individuels d'un entier ne sont pas mutables.

Vous devriez savoir pourquoi. Pensez-y.

Je n'aime pas le dire, mais malheureusement, nous en débattons parce que notre langue est nulle et nous essayons d'utiliser un seul mot, chaîne , pour décrire un concept ou une classe d'objets complexe et située dans le contexte.

Nous effectuons des calculs et des comparaisons avec les "chaînes". semblable à la façon dont nous faisons avec des nombres. Si les chaînes (ou les entiers) sont modifiables, nous devrions écrire un code spécial pour verrouiller leurs valeurs dans des formes locales immuables afin d'effectuer tout type de calcul de manière fiable. Par conséquent, il est préférable de penser à une chaîne comme à un identifiant numérique, mais au lieu d’avoir 16, 32 ou 64 bits de long, elle pourrait faire des centaines de bits.

Lorsque quelqu'un dit "chaîne", nous pensons tous à des choses différentes. Ceux qui y voient simplement un ensemble de personnages, sans but particulier, seront bien sûr consternés par le fait que quelqu'un vient de décider de ne pas pouvoir manipuler ces personnages. Mais la & string; chaîne " la classe n'est pas simplement un tableau de caractères. C'est un STRING , pas un char [] . Il existe certaines hypothèses de base sur le concept que nous appelons une "chaîne" et il peut généralement être décrit comme une unité atomique significative de données codées, telle qu'un nombre. Lorsque les gens parlent de "manipulation de chaînes", ils parlent peut-être réellement de manipuler des personnages pour créer des chaînes , et StringBuilder est idéal pour cela. Réfléchissez un peu à ce que le mot & string; chaîne " signifie vraiment.

Considérez un instant ce que ce serait si les chaînes étaient mutables. La fonction API suivante peut être amenée à renvoyer des informations à un autre utilisateur si la chaîne de nom d'utilisateur mutable est modifiée intentionnellement ou non par un autre thread alors que cette fonction l'utilise:

string GetPersonalInfo( string username, string password )
{
    string stored_password = DBQuery.GetPasswordFor( username );
    if (password == stored_password)
    {
        //another thread modifies the mutable 'username' string
        return DBQuery.GetPersonalInfoFor( username );
    }
}

La sécurité ne concerne pas seulement le "contrôle d'accès", elle concerne également la "sécurité" et la "garantie de l'exactitude". Si une méthode ne peut pas être facilement écrite et utilisée pour effectuer un simple calcul ou une comparaison de manière fiable, il n’est pas prudent de l’appeler, mais il serait prudent de remettre en question le langage de programmation lui-même.

L’immuabilité n’est pas si étroitement liée à la sécurité. Pour cela, au moins dans .NET, vous obtenez la classe SecureString.

C'est un compromis. Les chaînes vont dans le pool de chaînes et lorsque vous créez plusieurs chaînes identiques, elles partagent la même mémoire. Les concepteurs ont estimé que cette technique d’économie de mémoire fonctionnerait bien dans les cas les plus courants, car les programmes ont souvent tendance à écraser sur les mêmes chaînes.

L’inconvénient est que les concaténations génèrent un grand nombre de chaînes supplémentaires qui ne sont que transitionnelles et deviennent simplement des ordures, nuisant en réalité aux performances de la mémoire. Vous avez StringBuffer et StringBuilder (en Java, StringBuilder est également en .NET) à utiliser pour conserver la mémoire dans ces cas.

La décision d'avoir une chaîne mutable en C ++ pose de nombreux problèmes, consultez cet excellent article de Kelvin Henney sur Maladie de la vache folle .

COW = Copie en écriture.

Les chaînes en Java ne sont pas vraiment immuables, vous pouvez modifier leur valeur en utilisant la réflexion et / ou le chargement de classe. Vous ne devriez pas dépendre de cette propriété pour votre sécurité. Pour des exemples, voir: tour magique en Java

L’immuabilité est bonne. Voir Effective Java. Si vous deviez copier une chaîne à chaque fois que vous la transmettiez, il y aurait beaucoup de code source d'erreurs. Vous avez également une confusion quant aux modifications qui affectent quelles références. De la même manière qu'Integer doit être immuable pour se comporter comme int, Strings doit se comporter comme immuable pour se comporter comme des primitifs. En C ++, passer des chaînes par valeur s’effectue sans la mention explicite du code source.

Il existe une exception pour presque toutes les règles:

using System;
using System.Runtime.InteropServices;

namespace Guess
{
    class Program
    {
        static void Main(string[] args)
        {
            const string str = "ABC";

            Console.WriteLine(str);
            Console.WriteLine(str.GetHashCode());

            var handle = GCHandle.Alloc(str, GCHandleType.Pinned);

            try
            {
                Marshal.WriteInt16(handle.AddrOfPinnedObject(), 4, 'Z');

                Console.WriteLine(str);
                Console.WriteLine(str.GetHashCode());
            }
            finally
            {
                handle.Free();
            }
        }
    }
}

C'est principalement pour des raisons de sécurité. Il est beaucoup plus difficile de sécuriser un système si vous ne pouvez pas vous assurer que vos chaînes sont inviolables.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top