Un objet immuable est un objet dans lequel les champs internes (ou du moins, tous les champs internes qui affectent son comportement externe) ne peuvent pas être modifiés.
Les chaînes immuables présentent de nombreux avantages:
Performances: effectuez l'opération suivante:
String substring = fullstring.substring(x,y);
Le C sous-jacent de la méthode substring () est probablement quelque chose comme ceci:
// Assume string is stored like this:
struct String { char* characters; unsigned int length; };
// Passing pointers because Java is pass-by-reference
struct String* substring(struct String* in, unsigned int begin, unsigned int end)
{
struct String* out = malloc(sizeof(struct String));
out->characters = in->characters + begin;
out->length = end - begin;
return out;
}
Notez qu'aucun des caractères ne doit être copié! Si l'objet String était modifiable (les caractères pourraient changer plus tard), vous devrez alors copier tous les caractères, sinon les modifications apportées aux caractères de la sous-chaîne seront reflétées ultérieurement dans l'autre chaîne.
Concurrence: si la structure interne d'un objet immuable est valide, elle le sera toujours. Il n'y a aucune chance que différents threads puissent créer un état non valide au sein de cet objet. Par conséquent, les objets immuables sont Thread Safe .
Collecte des ordures: il est beaucoup plus facile pour le garbage collector de prendre des décisions logiques concernant les objets immuables.
Cependant, il y a aussi des inconvénients à l'immuabilité:
Performance: Attendez, je pensais que vous aviez dit que la performance était un avantage de l'immuabilité! Eh bien, c'est parfois, mais pas toujours. Prenez le code suivant:
foo = foo.substring(0,4) + "a" + foo.substring(5); // foo is a String
bar.replace(4,5,"a"); // bar is a StringBuilder
Les deux lignes remplacent toutes les deux le quatrième caractère par la lettre "a". Non seulement le deuxième morceau de code est plus lisible, mais il est plus rapide. Regardez comment vous devriez faire le code sous-jacent pour foo. Les sous-chaînes sont faciles, mais maintenant parce qu'il y a déjà un caractère à l'espace cinq et que quelque chose d'autre fait référence à foo, vous ne pouvez pas simplement le changer; vous devez copier la chaîne entière (bien sûr, certaines de ces fonctionnalités sont abstraites en fonctions dans le vrai C sous-jacent, mais le point ici est de montrer le code qui est exécuté tout en un seul endroit).
struct String* concatenate(struct String* first, struct String* second)
{
struct String* new = malloc(sizeof(struct String));
new->length = first->length + second->length;
new->characters = malloc(new->length);
int i;
for(i = 0; i < first->length; i++)
new->characters[i] = first->characters[i];
for(; i - first->length < second->length; i++)
new->characters[i] = second->characters[i - first->length];
return new;
}
// The code that executes
struct String* astring;
char a = 'a';
astring->characters = &a;
astring->length = 1;
foo = concatenate(concatenate(slice(foo,0,4),astring),slice(foo,5,foo->length));
Notez que concaténer est appelé deux fois, ce qui signifie que la chaîne entière doit être bouclée! Comparez cela au code C de l' bar
opération:
bar->characters[4] = 'a';
L'opération de chaîne mutable est évidemment beaucoup plus rapide.
En conclusion: dans la plupart des cas, vous voulez une chaîne immuable. Mais si vous avez besoin de faire beaucoup d'ajout et d'insertion dans une chaîne, vous avez besoin de la mutabilité pour la vitesse. Si vous souhaitez bénéficier des avantages de la sécurité et de la récupération de place de la concurrence, la clé consiste à conserver vos objets mutables en local par rapport à une méthode:
// This will have awful performance if you don't use mutable strings
String join(String[] strings, String separator)
{
StringBuilder mutable;
boolean first = true;
for(int i = 0; i < strings.length; i++)
{
if(!first) first = false;
else mutable.append(separator);
mutable.append(strings[i]);
}
return mutable.toString();
}
Étant donné que l' mutable
objet est une référence locale, vous n'avez pas à vous soucier de la sécurité des accès concurrents (un seul thread le touche). Et comme il n'est référencé nulle part ailleurs, il est uniquement alloué sur la pile, il est donc désalloué dès que l'appel de fonction est terminé (vous n'avez pas à vous soucier du garbage collection). Et vous bénéficiez de tous les avantages de performance de la mutabilité et de l'immuabilité.