Comment fonctionne cet extrait de code Java? (Pool de chaînes et reflection)

Le pool de chaînes Java couplé à la reflection peut produire des résultats inimaginables en Java:

import java.lang.reflect.Field; class MessingWithSsortingng { public static void main (Ssortingng[] args) { Ssortingng str = "Mario"; toLuigi(str); System.out.println(str + " " + "Mario"); } public static void toLuigi(Ssortingng original) { try { Field ssortingngValue = Ssortingng.class.getDeclaredField("value"); ssortingngValue.setAccessible(true); ssortingngValue.set(original, "Luigi".toCharArray()); } catch (Exception ex) { // Ignore exceptions } } } 

Le code ci-dessus sera imprimé:

 "Luigi Luigi" 

Qu’est-il arrivé à Mario?

Qu’est-il arrivé à Mario ??

Vous l’avez changé fondamentalement. Oui, avec la reflection, vous pouvez violer l’immuabilité des chaînes de caractères … et en raison de l’internement de chaînes, cela signifie que toute utilisation de “Mario” (sauf dans une expression constante de chaîne plus grande, qui aurait été résolue au moment de la compilation) comme “Luigi” dans le rest du programme.

Ce genre de chose explique pourquoi la reflection nécessite des permissions de sécurité …

Notez que l’expression str + " " + "Mario" n’effectue aucune concaténation à la compilation, en raison de l’associativité à gauche de + . C’est effectivement (str + " ") + "Mario" , c’est pourquoi vous voyez toujours Luigi Luigi . Si vous changez le code pour:

 System.out.println(str + (" " + "Mario")); 

… alors vous verrez Luigi Mario comme le compilateur aura interné " Mario" sur une chaîne différente de "Mario" .

C’était à Luigi. Les chaînes en Java sont immuables; ainsi, le compilateur peut interpréter toutes les mentions de "Mario" comme des références au même élément de pool de constantes Ssortingng (en gros, “emplacement mémoire”). Vous avez utilisé la reflection pour modifier cet élément. donc tout "Mario" dans votre code est maintenant comme si vous écriviez "Luigi" .

Pour expliquer un peu plus les réponses existantes, jetons un coup d’œil à votre code d’octet généré (Seule la méthode main() ici).

Code d'octet

Désormais, toute modification du contenu de cet emplacement affectera les deux références (et toutes les autres que vous donnez également).

Les littéraux de chaîne sont stockés dans le pool de chaînes et leur valeur canonique est utilisée. Les deux littéraux "Mario" ne sont pas simplement des chaînes de même valeur, ils sont le même object . Manipuler l’un d’entre eux (en utilisant la reflection) en modifiera “les deux”, car ils ne sont que deux références au même object.

Vous venez de changer le Ssortingng du pool de constantes Mario en Luigi qui a été référencé par plusieurs Ssortingng s, donc chaque littéral de référence Mario est maintenant Luigi .

 Field ssortingngValue = Ssortingng.class.getDeclaredField("value"); 

Vous avez récupéré le champ de value nommé char[] dans la classe Ssortingng

 ssortingngValue.setAccessible(true); 

Rendez-le accessible.

 ssortingngValue.set(original, "Luigi".toCharArray()); 

Vous avez changé original champ Ssortingng original en Luigi . Mais l’original est Mario le littéral Ssortingng et littéral appartient au pool Ssortingng et tous sont internés . Ce qui signifie que tous les littéraux qui ont le même contenu font référence à la même adresse mémoire.

 Ssortingng a = "Mario";//Created in Ssortingng pool Ssortingng b = "Mario";//Refers to the same Mario of Ssortingng pool a == b//TRUE //You changed 'a' to Luigi and 'b' don't know that //'a' has been internally changed and //'b' still refers to the same address. 

Fondamentalement, vous avez changé le pool Mario of Ssortingng qui a été reflété dans tous les champs de référence. Si vous créez un Object Ssortingng (c.-à-d. Une new Ssortingng("Mario") ) au lieu de littéral, vous ne rencontrerez pas ce problème car vous aurez deux Mario différents.

Les autres réponses expliquent de manière adéquate ce qui se passe. Je voulais simplement append que cela ne fonctionne que si aucun gestionnaire de sécurité n’est installé. Lorsque vous exécutez du code à partir de la ligne de commande par défaut, il n’y en a pas et vous pouvez faire des choses comme ça. Cependant, dans un environnement où le code sécurisé est mélangé avec du code non approuvé, tel qu’un serveur d’applications dans un environnement de production ou un sandbox d’applet dans un navigateur, un gestionnaire de sécurité est généralement présent et vous ne seriez pas autorisé à effectuer ce genre de manipulation. c’est un trou de sécurité moins terrible qu’il n’y paraît.

Autre point associé: vous pouvez utiliser le pool constant pour améliorer les performances des comparaisons de chaînes dans certaines circonstances, en utilisant la méthode Ssortingng.intern() .

Cette méthode renvoie l’instance de Ssortingng avec le même contenu que la chaîne sur laquelle elle est appelée à partir du pool de constantes Ssortingng, en l’ajoutant si elle n’est pas encore présente. En d’autres termes, après avoir utilisé intern() , toutes les chaînes ayant le même contenu sont garanties comme étant la même instance de Ssortingng et comme toute constante de chaîne avec ce contenu, ce qui signifie que vous pouvez utiliser l’opérateur égal ( == ). .

Ceci est juste un exemple qui n’est pas très utile en soi, mais il illustre bien le point:

 class Key { Key(Ssortingng keyComponent) { this.keyComponent = keyComponent.intern(); } public boolean equals(Object o) { // Ssortingng comparison using the equals operator allowed due to the // intern() in the constructor, which guarantees that all values // of keyComponent with the same content will refer to the same // instance of Ssortingng: return (o instanceof Key) && (keyComponent == ((Key) o).keyComponent); } public int hashCode() { return keyComponent.hashCode(); } boolean isSpecialCase() { // Ssortingng comparison using equals operator valid due to use of // intern() in constructor, which guarantees that any keyComponent // with the same contents as the SPECIAL_CASE constant will // refer to the same instance of Ssortingng: return keyComponent == SPECIAL_CASE; } private final Ssortingng keyComponent; private static final Ssortingng SPECIAL_CASE = "SpecialCase"; } 

Ce petit truc ne vaut pas la peine de concevoir votre code, mais il convient de garder à l’esprit le jour où vous remarquerez qu’un peu plus de code sensible aux performances pourrait accélérer un peu plus la vitesse en utilisant l’opérateur == sur une chaîne avec utilisation judicieuse de intern() .