Que sont les objets immuables ?
Un objet String est immuable, mais cela signifie simplement que vous ne pouvez pas modifier sa valeur en appelant ses méthodes publiques.
Comme nous le savons tous, en Java, la classe String est immuable. Alors, que sont exactement les objets immuables ? Vous pouvez y penser de cette façon : si un objet ne peut pas changer d’état après sa création, alors l’objet est immuable. L'état ne peut pas être modifié, ce qui signifie que les variables membres au sein de l'objet ne peuvent pas être modifiées, y compris les valeurs des types de données de base. Les variables des types référence ne peuvent pas pointer vers d'autres objets, et l'état des objets pointés par les types référence ne peut pas. être changé.
Distinguer les objets et les références d'objet
Pour les débutants Java, il y a toujours des doutes sur le fait que String soit un objet immuable. Regardez le code ci-dessous :
Chaîne s = "ABCabc";System.out.println("s = " + s);s = "123456";System.out.println("s = " + s);Le résultat imprimé est :
s = ABCabc s = 123456
Créez d'abord un objet String s, puis laissez la valeur de s être "ABCabc", puis laissez la valeur de s être "123456". Comme le montrent les résultats imprimés, la valeur de s a effectivement changé. Alors pourquoi dites-vous encore que les objets String sont immuables ? En fait, il y a ici un malentendu : s est simplement une référence à un objet String, pas à l'objet lui-même. L'objet est une zone mémoire dans la mémoire. Plus il y a de variables membres, plus l'espace occupé par cette zone mémoire est grand. Une référence n'est qu'une donnée de 4 octets qui stocke l'adresse de l'objet vers lequel elle pointe. L'objet est accessible via cette adresse.
En d'autres termes, s est juste une référence qui pointe vers un objet spécifique. Lorsque s="123456"; après l'exécution de ce code, un nouvel objet "123456" est créé et la référence s pointe à nouveau vers cet objet cœur. , l'objet d'origine "ABCabc" existe toujours dans la mémoire et n'a pas changé. La structure de la mémoire est illustrée dans la figure ci-dessous :
Une différence entre Java et C++ est qu'en Java, il est impossible d'exploiter directement l'objet lui-même. Tous les objets sont pointés par une référence. Vous devez utiliser cette référence pour accéder à l'objet lui-même, notamment pour obtenir la valeur de la variable membre et la modifier. la variable membre de l'objet. Appeler les méthodes de l'objet, etc. En C++, il y a trois choses : les références, les objets et les pointeurs, qui peuvent tous accéder aux objets. En fait, les références en Java et les pointeurs en C++ sont conceptuellement similaires. Ce sont les valeurs d'adresse des objets stockés en mémoire. Cependant, en Java, les références perdent une certaine flexibilité. Par exemple, les références en Java ne peuvent pas être utilisées comme l'addition et la soustraction. sont exécutés comme des pointeurs en C++.
Pourquoi les objets String sont-ils immuables ?
Pour comprendre l'immuabilité de String, jetez d'abord un œil aux variables membres de la classe String. Dans JDK1.6, les variables membres de String incluent les éléments suivants :
public final class String implémente java.io.Seriallessly, Comparable<String>, CharSequence{ /** La valeur est utilisée pour le stockage des caractères */ private final char value[] /** Le décalage est le premier index du stockage. qui est utilisé. */ private final int offset; /** Le nombre est le nombre de caractères dans la chaîne. */ private final int count /** Cache le code de hachage de la chaîne */ private int hash; Par défaut 0Dans JDK1.7, la classe String a apporté quelques modifications, modifiant principalement le comportement lors de l'exécution de la méthode substring, ce qui n'est pas lié au sujet de cet article. Il n'y a que deux variables membres principales de la classe String dans JDK1.7 :
public final class String implémente java.io.Seriallessly, Comparable<String>, CharSequence { /** La valeur est utilisée pour le stockage des caractères */ private final char value[] /** Cache le code de hachage de la chaîne */ hachage int privé ; // Par défaut : 0Comme le montre le code ci-dessus, la classe String en Java est en fait une encapsulation d'un tableau de caractères. Dans JDK6, value est un tableau encapsulé par String, offset est la position de départ de String dans le tableau de valeurs et count est le nombre de caractères occupés par String. Dans JDK7, il n'y a qu'une seule variable de valeur, c'est-à-dire que tous les caractères de la valeur appartiennent à l'objet String. Ce changement n’affecte pas la discussion de cet article. De plus, il existe une variable membre de hachage, qui est un cache de la valeur de hachage de l'objet String. Cette variable membre n'est pas non plus pertinente pour la discussion de cet article. En Java, les tableaux sont aussi des objets (veuillez vous référer à mon article précédent Caractéristiques des tableaux en Java). La valeur est donc simplement une référence qui pointe vers un véritable objet tableau. En fait, après avoir exécuté le code String s = « ABCabc » ;, la véritable disposition de la mémoire devrait ressembler à ceci :
Les trois variables value, offset et count sont toutes privées et aucune méthode publique telle que setValue, setOffset et setCount n'est fournie pour modifier ces valeurs, donc String ne peut pas être modifié en dehors de la classe String. C'est-à-dire qu'une fois initialisé, il ne peut plus être modifié et ces trois membres ne sont pas accessibles en dehors de la classe String. De plus, les trois variables value, offset et count sont toutes définitives, ce qui signifie qu'au sein de la classe String, une fois ces trois valeurs initialisées, elles ne peuvent plus être modifiées. L'objet String peut donc être considéré comme immuable.
Donc, dans String, il existe évidemment des méthodes, et les appeler peut obtenir la valeur modifiée. Ces méthodes incluent substring, replace, replaceAll, toLowerCase, etc. Par exemple, le code suivant :
Chaîne a = "ABCabc"; System.out.println("a = " + a); a = a.replace('A', 'a');Le résultat imprimé est :
a = ABCabca = aBCabc
Alors la valeur de a semble avoir changé, mais en fait c'est le même malentendu. Encore une fois, a n'est qu'une référence, pas un véritable objet chaîne. Lors de l'appel à a.replace('A', 'a'), la méthode crée en interne un nouvel objet String et réaffecte le nouvel objet à Cited a. Le code source de la méthode replace dans String peut illustrer le problème :
Les lecteurs peuvent vérifier d'autres méthodes par eux-mêmes. Ils recréent tous un nouvel objet String à l'intérieur de la méthode et renvoient ce nouvel objet. L'objet d'origine ne sera pas modifié. C'est pourquoi les méthodes comme replace, substring, toLowerCase, etc. ont toutes des valeurs de retour. C'est aussi pourquoi appeler comme suit ne change pas la valeur de l'objet :
Chaîne ss = "123456";System.out.println("ss = " + ss);ss.replace('1', '0');System.out.println("ss = " + ss);Imprimez le résultat :
ss = 123456 ss = 123456
Les objets String sont-ils vraiment immuables ?
Comme le montre ce qui précède, les variables membres de String sont privées finales, c'est-à-dire qu'elles ne peuvent pas être modifiées après l'initialisation. Parmi ces membres, la valeur est spéciale car il s'agit d'une variable de référence et non d'un objet réel. la valeur est modifiée par final, ce qui signifie que final ne peut plus pointer vers d'autres objets du tableau, puis-je changer le tableau vers lequel la valeur pointe ? Par exemple, remplacez le caractère situé à une certaine position dans le tableau par un trait de soulignement "_". Au moins, nous ne pouvons pas le faire dans le code ordinaire que nous écrivons nous-mêmes, car nous ne pouvons pas du tout accéder à cette référence de valeur, et encore moins modifier le tableau via cette référence.
Alors, comment pouvons-nous accéder aux membres privés ? C'est vrai, en utilisant la réflexion, vous pouvez refléter l'attribut value dans l'objet String, puis modifier la structure du tableau via la référence de valeur obtenue. Voici l'exemple de code :
public static void testReflection() throws Exception { //Créez la chaîne "Hello World" et attribuez-la à la référence s String s = "Hello World"; World //Récupère le champ de valeur dans la classe String Field valueFieldOfString = String.class.getDeclaredField("value"); //Modifie l'autorisation d'accès de l'attribut de valeur valueFieldOfString.setAccessible(true); //Obtenir la valeur de l'attribut value sur l'objet s char[] value = (char[]) valueFieldOfString.get(s); //Modifier le cinquième caractère du tableau référencé par value value[5] = '_' ; System.out.println("s = " + s); //Hello_World }Le résultat imprimé est :
s = Bonjour Monde s = Bonjour_Monde
Dans ce processus, s fait toujours référence au même objet String, mais avant et après la réflexion, l'objet String change. En d'autres termes, l'objet dit « immuable » peut être modifié par réflexion. Mais en général, nous ne le faisons pas. Cet exemple de réflexion peut aussi illustrer un problème : si l'état d'un objet et des autres objets qui le composent peuvent changer, alors cet objet n'est probablement pas un objet immuable. Par exemple, un objet Car est combiné avec un objet Wheel Bien que l'objet Wheel soit déclaré privé final, l'état interne de l'objet Wheel peut changer, de sorte que l'objet Car ne peut pas être garanti comme étant immuable.