Comment fonctionne cet extrait de code Java? (Pool de chaînes et réflexion) [dupliquer]


Cette question a déjà une réponse ici:

Le pool de chaînes Java associé à la réflexion peut produire un résultat inimaginable en Java:

import java.lang.reflect.Field;

class MessingWithString {
    public static void main (String[] args) {
        String str = "Mario";
        toLuigi(str);
        System.out.println(str + " " + "Mario");
    }

    public static void toLuigi(String original) {
        try {
            Field stringValue = String.class.getDeclaredField("value");
            stringValue.setAccessible(true);
            stringValue.set(original, "Luigi".toCharArray());
        } catch (Exception ex) {
            // Ignore exceptions
        }
    }
}

Le code ci-dessus imprimera:

"Luigi Luigi" 

Qu'est-il arrivé à Mario?

Author: aliteralmind, 2015-09-17

7 answers

Qu'est-il arrivé à Mario ??

Vous l'avez changé, fondamentalement. Oui, avec la réflexion, vous pouvez violer l'immutabilité des chaînes... et en raison de l'internement de chaînes, cela signifie que toute utilisation de "Mario" (autre que dans une expression de constante de chaîne plus grande, qui aurait été résolue au moment de la compilation) finira comme "Luigi" dans le reste du programme.

Ce genre de chose est la raison pour laquelle reflection nécessite des autorisations de sécurité...

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

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

... ensuite, vous verrez Luigi Mario car le compilateur aura interné " Mario" dans une chaîne différente de "Mario".

 95
Author: Jon Skeet, 2015-09-17 07:48:41

Il a été réglé sur 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 de chaîne (en gros, "emplacement de mémoire"). Vous avez utilisé la réflexion pour changer cet élément; donc tous les "Mario" dans votre code sont maintenant comme si vous aviez écrit "Luigi".

 24
Author: Amadan, 2015-09-17 07:56:28

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

Byte Code

Maintenant, toute modification du contenu de cet emplacement affectera à la fois les références (Et toute autre que vous donnez aussi).

 16
Author: Codebender, 2015-09-17 06:41:18

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 seulement des chaînes avec la même valeur, ils sont le même objet. Manipuler l'un d'eux (en utilisant la réflexion) modifiera "les deux", car ce ne sont que deux références au même objet.

 9
Author: Mureinik, 2015-09-17 06:35:30

Vous venez de modifier le String de constante de Chaîne piscine Mario de Luigi, qui a été référencé par plusieurs Strings, de sorte que chaque référencement littérale Mario est maintenant Luigi.

Field stringValue = String.class.getDeclaredField("value");

, Vous avez récupéré le char[] nommé value champ de la classe String

stringValue.setAccessible(true);

Rendre accessible.

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

Vous avez changé original String champ Luigi. Mais l'original est Mario le String littéral et littérale appartient à la String piscine et tous sont interné. Ce qui signifie que tous les littéraux qui ont le même contenu se réfèrent à la même adresse mémoire.

String a = "Mario";//Created in String pool
String b = "Mario";//Refers to the same Mario of String 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 Mario de String pool qui a reflété dans tous les champs de référencement. Si vous créez String Object (c'est-à-dire new String("Mario")) au lieu de littéral, vous ne ferez pas face à ce comportement car vous aurez deux Marios différents .

 8
Author: coder-croc, 2015-09-17 07:21:37

Les autres réponses expliquent adéquatement ce qui se passe. Je voulais juste ajouter le fait que cela ne fonctionne que s'il n'y a pas de security manager 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 de confiance est mélangé avec du code non approuvé, comme un serveur d'applications dans un environnement de production ou un bac à sable applet dans un navigateur, il y aurait généralement un gestionnaire de sécurité présent et vous ne pas être autorisé à ce genre de manigances, donc c'est moins d'un trou de sécurité terrible comme il semble.

 5
Author: Pepijn Schmitz, 2015-09-17 10:33:23

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

Cette méthode renvoie l'instance de String avec le même contenu que la Chaîne sur laquelle elle est appelée à partir du pool de constantes de chaîne, en l'ajoutant si elle n'est pas encore présente. En d'autres termes, après avoir utilisé intern(), toutes les chaînes avec le même contenu sont garanties d'être la même instance de chaîne les unes que les autres et comme toutes les constantes de chaîne avec ces contenus, ce qui signifie que vous pouvez ensuite utiliser l'opérateur equals (==) sur eux.

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

class Key {
    Key(String keyComponent) {
        this.keyComponent = keyComponent.intern();
    }

    public boolean equals(Object o) {
        // String 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 String:
        return (o instanceof Key) && (keyComponent == ((Key) o).keyComponent);
    }

    public int hashCode() {
        return keyComponent.hashCode();
    }

    boolean isSpecialCase() {
        // String 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 String:
        return keyComponent == SPECIAL_CASE;
    }

    private final String keyComponent;

    private static final String SPECIAL_CASE = "SpecialCase";
}

Cette petite astuce ne vaut pas la peine de concevoir votre code, mais il vaut la peine de garder à l'esprit le jour où vous remarquerez qu'un peu plus de vitesse pourrait être dégagée d'un peu de code sensible aux performances en utilisant l'opérateur == sur une chaîne avec une utilisation judicieuse de intern().

 3
Author: Pepijn Schmitz, 2015-09-22 08:52:02