Nous savons tous que cela String
est immuable en Java, mais vérifiez le code suivant:
String s1 = "Hello World";
String s2 = "Hello World";
String s3 = s1.substring(6);
System.out.println(s1); // Hello World
System.out.println(s2); // Hello World
System.out.println(s3); // World
Field field = String.class.getDeclaredField("value");
field.setAccessible(true);
char[] value = (char[])field.get(s1);
value[6] = 'J';
value[7] = 'a';
value[8] = 'v';
value[9] = 'a';
value[10] = '!';
System.out.println(s1); // Hello Java!
System.out.println(s2); // Hello Java!
System.out.println(s3); // World
Pourquoi ce programme fonctionne-t-il ainsi? Et pourquoi la valeur de s1
et a-t-elle s2
changé, mais pas s3
?
java
string
reflection
immutability
Darshan Patel
la source
la source
(Integer)1+(Integer)2=42
en jouant avec la mise en cache automatique; (Disgruntled-Bomb-Java-Edition) ( thedailywtf.com/Articles/Disgruntled-Bomb-Java-Edition.aspx )Réponses:
String
est immuable * mais cela signifie uniquement que vous ne pouvez pas le modifier à l'aide de son API publique.Ce que vous faites ici, c'est contourner l'API normale, en utilisant la réflexion. De la même manière, vous pouvez modifier les valeurs des énumérations, modifier la table de recherche utilisée dans la zone de saisie automatique entière, etc.
Maintenant, la raison
s1
et las2
valeur de changement, c'est qu'ils se réfèrent tous deux à la même chaîne internée. Le compilateur fait cela (comme mentionné dans d'autres réponses).La raison
s3
n'est pas vraiment un peu surprenante pour moi, car je pensais que cela partagerait levalue
tableau ( c'était le cas dans la version précédente de Java , avant Java 7u6). Cependant, en regardant le code source deString
, nous pouvons voir que levalue
tableau de caractères pour une sous-chaîne est réellement copié (en utilisantArrays.copyOfRange(..)
). C'est pourquoi cela reste inchangé.Vous pouvez installer un
SecurityManager
, pour éviter que du code malveillant fasse de telles choses. Mais gardez à l'esprit que certaines bibliothèques dépendent de l'utilisation de ce type d'astuces de réflexion (généralement des outils ORM, des bibliothèques AOP, etc.).*) J'ai initialement écrit que les
String
s ne sont pas vraiment immuables, juste "efficaces immuables". Cela peut être trompeur dans l'implémentation actuelle deString
, où levalue
tableau est en effet marquéprivate final
. Cependant, il convient de noter qu'il n'y a aucun moyen de déclarer un tableau en Java immuable, il faut donc veiller à ne pas l'exposer en dehors de sa classe, même avec les modificateurs d'accès appropriés.Comme ce sujet semble extrêmement populaire, voici quelques suggestions de lecture supplémentaire: le discours de réflexion sur la folie de Heinz Kabutz de JavaZone 2009, qui couvre beaucoup de questions dans le PO, ainsi que d'autres réflexions ... eh bien ... la folie.
Il explique pourquoi cela est parfois utile. Et pourquoi, la plupart du temps, vous devriez l'éviter. :-)
la source
String
internement fait partie du JLS ( "un littéral de chaîne fait toujours référence à la même instance de la classe String" ). Mais je suis d'accord, ce n'est pas une bonne pratique de compter sur les détails d'implémentation de laString
classe.substring
copies plutôt que d'utiliser une "section" du tableau existant, est sinon si j'avais une énorme chaînes
et en ai retiré une minuscule sous-chaîne appeléet
, et j'ai ensuite abandonnés
mais conservét
, alors le grand tableau serait maintenu en vie (pas de déchets ramassés). Alors peut-être qu'il est plus naturel que chaque valeur de chaîne ait son propre tableau associé?String
instance devait transporter des variables pour se souvenir du décalage dans le tableau et la longueur référencés. C'est une surcharge à ne pas ignorer étant donné le nombre total de chaînes et le rapport typique entre les chaînes normales et les sous-chaînes dans une application. Puisqu'ils devaient être évalués pour chaque opération de chaîne, cela signifiait ralentir chaque opération de chaîne uniquement pour le bénéfice d'une seule opération, une sous-chaîne bon marché.byte[]
chaînes pour les chaînes ASCII etchar[]
pour d'autres implique que chaque opération doit vérifier de quel type de chaîne il s'agit avant en fonctionnement. Cela empêche l'inclusion du code dans les méthodes à l'aide de chaînes, ce qui est la première étape d'optimisations supplémentaires utilisant les informations de contexte de l'appelant. C'est un gros impact.En Java, si deux variables primitives de chaîne sont initialisées sur le même littéral, il attribue la même référence aux deux variables:
C'est la raison pour laquelle la comparaison est vraie. La troisième chaîne est créée à l'aide de
substring()
ce qui crée une nouvelle chaîne au lieu de pointer vers la même.Lorsque vous accédez à une chaîne à l'aide de la réflexion, vous obtenez le pointeur réel:
Donc, changer cela changera la chaîne contenant un pointeur, mais comme cela
s3
est créé avec une nouvelle chaîne,substring()
cela ne changera pas.la source
intern
manuellement sur une chaîne non littérale et profiter des avantages.intern
judicieusement. Tout interner ne vous rapporte pas grand-chose et peut être la source de moments de grattage de tête lorsque vous ajoutez de la réflexion au mixage.Test1
etTest1
sont incompatibles avectest1==test2
et ne respectent pas les conventions de dénomination java.Vous utilisez la réflexion pour contourner l'immuabilité de String - c'est une forme "d'attaque".
Il existe de nombreux exemples que vous pouvez créer comme ceci (par exemple, vous pouvez même instancier un
Void
objet aussi), mais cela ne signifie pas que String n'est pas "immuable".Il existe des cas d'utilisation où ce type de code peut être utilisé à votre avantage et être un "bon codage", comme la suppression des mots de passe de la mémoire le plus tôt possible (avant GC) .
Selon le responsable de la sécurité, vous ne pourrez peut-être pas exécuter votre code.
la source
Vous utilisez la réflexion pour accéder aux "détails d'implémentation" de l'objet chaîne. L'immuabilité est la caractéristique de l'interface publique d'un objet.
la source
Les modificateurs de visibilité et final (c'est-à-dire l'immuabilité) ne sont pas une mesure par rapport au code malveillant en Java; ce ne sont que des outils pour se protéger contre les erreurs et pour rendre le code plus maintenable (l'un des gros arguments de vente du système). C'est pourquoi vous pouvez accéder aux détails d'implémentation internes comme le tableau de caractères de support pour
String
s via la réflexion.Le deuxième effet que vous voyez est que tout
String
change alors qu'il semble que vous ne changiez ques1
. C'est une certaine propriété des littéraux Java String qu'ils sont automatiquement internés, c'est-à-dire mis en cache. Deux littéraux String avec la même valeur seront en fait le même objet. Lorsque vous créez une chaîne avecnew
elle, elle ne sera pas internée automatiquement et vous ne verrez pas cet effet.#substring
jusqu'à récemment (Java 7u6) fonctionnait de manière similaire, ce qui aurait expliqué le comportement dans la version originale de votre question. Il n'a pas créé de nouveau tableau de caractères de support mais a réutilisé celui de la chaîne d'origine; il vient de créer un nouvel objet String qui utilise un décalage et une longueur pour ne présenter qu'une partie de ce tableau. Cela a généralement fonctionné car les cordes sont immuables - à moins que vous ne les contourniez. Cette propriété de#substring
signifiait également que l'intégralité de la chaîne d'origine ne pouvait pas être récupérée lorsque une sous-chaîne plus courte créée à partir d'elle existait toujours.Depuis Java actuel et votre version actuelle de la question, il n'y a pas de comportement étrange
#substring
.la source
final
même par la réflexion. En outre, comme mentionné dans une autre réponse, depuis Java 7u6,#substring
ne partage pas de tableaux.final
a changé au fil du temps ...: -O Selon le discours "Reflection Madness" de Heinz que j'ai posté dans l'autre fil,final
signifiait final dans JDK 1.1, 1.3 et 1.4, mais pourrait être modifié en utilisant la réflexion en utilisant 1.2 toujours , et dans 1.5 et 6 dans la plupart des cas ...final
les champs peuvent être modifiés via dunative
code comme le fait le framework de sérialisation lors de la lecture des champs d'une instance sérialisée ainsi queSystem.setOut(…)
qui modifie laSystem.out
variable finale . Ce dernier est la caractéristique la plus intéressante car la réflexion avec dérogation d'accès ne peut pas changer lesstatic final
champs.L'immuabilité des chaînes est du point de vue de l'interface. Vous utilisez la réflexion pour contourner l'interface et modifier directement les internes des instances de chaîne.
s1
ets2
sont tous deux modifiés car ils sont tous deux affectés à la même instance de chaîne "interne". Vous pouvez en savoir un peu plus sur cette partie de cet article sur l'égalité des chaînes et l'internement. Vous pourriez être surpris de découvrir que dans votre exemple de code, less1 == s2
retourstrue
!la source
Quelle version de Java utilisez-vous? Depuis Java 1.7.0_06, Oracle a modifié la représentation interne de String, en particulier la sous-chaîne.
Citant de la représentation de chaîne interne d' Oracle Tunes Java :
Avec ce changement, cela peut arriver sans réflexion (???).
la source
Il y a vraiment deux questions ici:
Point 1: à l'exception de la ROM, il n'y a pas de mémoire immuable dans votre ordinateur. De nos jours, même la ROM est parfois accessible en écriture. Il y a toujours du code quelque part (que ce soit le noyau ou le code natif qui contourne votre environnement géré) qui peut écrire dans votre adresse mémoire. Donc, dans la «réalité», non, ils ne sont pas absolument immuables.
Point 2: cela est dû au fait que la sous-chaîne alloue probablement une nouvelle instance de chaîne, qui copie probablement le tableau. Il est possible d'implémenter la sous-chaîne de telle manière qu'elle ne fasse pas de copie, mais cela ne signifie pas qu'elle le fait. Il y a des compromis à faire.
Par exemple, la détention d'une référence doit-elle maintenir une
reallyLargeString.substring(reallyLargeString.length - 2)
grande quantité de mémoire en vie ou seulement quelques octets?Cela dépend de la façon dont la sous-chaîne est implémentée. Une copie complète gardera moins de mémoire en vie, mais elle s'exécutera légèrement plus lentement. Une copie superficielle gardera plus de mémoire en vie, mais ce sera plus rapide. L'utilisation d'une copie complète peut également réduire la fragmentation du segment de mémoire, car l'objet chaîne et son tampon peuvent être alloués en un seul bloc, par opposition à 2 allocations de segment distinctes.
Dans tous les cas, il semble que votre machine virtuelle Java ait choisi d'utiliser des copies complètes pour les appels de sous-chaîne.
la source
Pour ajouter à la réponse de @ haraldK - il s'agit d'un hack de sécurité qui pourrait entraîner un impact sérieux sur l'application.
La première chose est une modification d'une chaîne constante stockée dans un pool de chaînes. Lorsque la chaîne est déclarée en tant que
String s = "Hello World";
, elle est placée dans un pool d'objets spécial pour une réutilisation potentielle supplémentaire. Le problème est que le compilateur placera une référence à la version modifiée au moment de la compilation et une fois que l'utilisateur modifie la chaîne stockée dans ce pool au moment de l'exécution, toutes les références dans le code pointeront vers la version modifiée. Cela entraînerait un bogue suivant:Imprime:
Il y a eu un autre problème que j'ai rencontré lorsque j'implémentais un calcul lourd sur de telles chaînes risquées. Il y a eu un bug qui s'est produit comme 1 fois sur 1000000 pendant le calcul, ce qui a rendu le résultat non déterministe. J'ai pu trouver le problème en éteignant le JIT - J'obtenais toujours le même résultat avec JIT éteint. Je suppose que la raison en est ce hack de sécurité String qui a rompu certains des contrats d'optimisation JIT.
la source
String.format("")
dans l'une des boucles internes. Il y a une chance que ce soit un autre problème que JIT, mais je pense que c'était JIT, car ce problème n'a jamais été reproduit après l'ajout de ce no-op.String
internes, ne vous est pas venu à l'esprit?final
garanties de sécurité des threads de champ qui se cassent lors de la modification des données après la construction de l'objet. Vous pouvez donc le voir comme un problème JIT ou un problème MT comme vous le souhaitez. Le vrai problème est de piraterString
et de modifier les données qui devraient être immuables.Selon le concept de regroupement, toutes les variables String contenant la même valeur pointeront vers la même adresse mémoire. Par conséquent, s1 et s2, contenant tous deux la même valeur de «Hello World», pointeront vers le même emplacement de mémoire (disons M1).
D'un autre côté, s3 contient «World», il indiquera donc une allocation de mémoire différente (disons M2).
Alors maintenant, ce qui se passe, c'est que la valeur de S1 est modifiée (en utilisant la valeur char []). Ainsi, la valeur à l'emplacement de mémoire M1 pointé à la fois par s1 et s2 a été modifiée.
Par conséquent, l'emplacement de mémoire M1 a été modifié, ce qui entraîne une modification de la valeur de s1 et s2.
Mais la valeur de l'emplacement M2 reste inchangée, donc s3 contient la même valeur d'origine.
la source
La raison pour laquelle s3 ne change pas est qu'en Java, lorsque vous effectuez une sous-chaîne, le tableau de caractères de valeur pour une sous-chaîne est copié en interne (à l'aide de Arrays.copyOfRange ()).
s1 et s2 sont identiques car en Java ils font tous deux référence à la même chaîne interne. C'est par conception en Java.
la source
String.substring(int, int)
changé avec Java 7u6. Avant 7u6, la machine virtuelle Java serait tout simplement garder un pointeur vers l'originalString
est enchar[]
même temps avec un indice et la longueur. Après 7u6, il copie la sous-chaîne dans un nouveau.String
Il y a des avantages et des inconvénients.La chaîne est immuable, mais par réflexion, vous êtes autorisé à modifier la classe String. Vous venez de redéfinir la classe String comme modifiable en temps réel. Vous pouvez redéfinir les méthodes pour qu'elles soient publiques ou privées ou statiques si vous le souhaitez.
la source
[Avis de non-responsabilité, il s'agit d'un style de réponse délibérément subjectif car je pense qu'une réponse plus «ne fais pas ça à la maison les enfants» est justifiée]
Le péché est la ligne
field.setAccessible(true);
qui dit violer l'API publique en autorisant l'accès à un champ privé. C'est un trou de sécurité géant qui peut être verrouillé en configurant un gestionnaire de sécurité.Le phénomène dans la question sont des détails d'implémentation que vous ne verriez jamais lorsque vous n'utilisez pas cette ligne de code dangereuse pour violer les modificateurs d'accès via la réflexion. Clairement, deux chaînes (normalement) immuables peuvent partager le même tableau de caractères. Le fait qu'une sous-chaîne partage le même tableau dépend si elle le peut et si le développeur a pensé le partager. Normalement, ce sont des détails d'implémentation invisibles que vous ne devriez pas avoir à connaître à moins que vous ne tiriez le modificateur d'accès à travers la tête avec cette ligne de code.
Ce n'est tout simplement pas une bonne idée de s'appuyer sur de tels détails qui ne peuvent pas être expérimentés sans violer les modificateurs d'accès en utilisant la réflexion. Le propriétaire de cette classe ne prend en charge que l'API publique normale et est libre d'apporter des modifications d'implémentation à l'avenir.
Cela dit, la ligne de code est vraiment très utile lorsque vous avez un pistolet qui vous tient la tête vous obligeant à faire des choses aussi dangereuses. L'utilisation de cette porte dérobée est généralement une odeur de code que vous devez mettre à niveau vers un meilleur code de bibliothèque où vous n'avez pas à pécher. Une autre utilisation courante de cette ligne de code dangereuse est d'écrire un "framework vaudou" (orm, conteneur d'injection, ...). Beaucoup de gens deviennent religieux au sujet de tels cadres (pour et contre eux), donc j'éviterai d'inviter à une guerre des flammes en ne disant rien d'autre que la grande majorité des programmeurs n'ont pas à y aller.
la source
Les chaînes sont créées dans la zone permanente de la mémoire de tas JVM. Alors oui, c'est vraiment immuable et ne peut pas être changé après avoir été créé. Parce que dans la JVM, il existe trois types de mémoire de tas: 1. Jeune génération 2. Ancienne génération 3. Génération permanente.
Lorsqu'un objet est créé, il va dans la zone de segment de mémoire de la jeune génération et la zone PermGen réservée au regroupement de chaînes.
Voici plus de détails, vous pouvez aller chercher plus d'informations à partir de: Comment fonctionne Garbage Collection en Java .
la source
La chaîne est de nature immuable, car il n'existe aucune méthode pour modifier l'objet String. C'est la raison pour laquelle ils ont introduit les classes StringBuilder et StringBuffer
la source