How does this piece of Java code work? (String pool and reflection)

A Java string pool combined with reflection can produce some unimaginable results in 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 } } } 

Above the code will be printed:

 "Luigi Luigi" 

What happened to Mario?

+82
java string reflection string-pool
Sep 17 '15 at 6:29
source share
7 answers

What happened to Mario?

You changed it, basically. Yes, with reflection, you can violate the immutability of strings ... and because of string interning, this means that any use of "Mario" (except for expressing a larger string constant that would be allowed at compile time) will end up like "Luigi" in the rest parts of the program.

This is because security permissions are required for reflection ...

Note that the expression str + " " + "Mario" does not concatenate compile time due to left + associativity. It is efficient (str + " ") + "Mario" , so you still see Luigi Luigi . If you change the code to:

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

... then you will see Luigi Mario , as the compiler will intern " Mario" for another line up to "Mario" .

+94
Sep 17 '15 at 6:34
source share

He was tuned to Luigi. Strings in Java are immutable; in this way, the compiler can interpret all references to "Mario" as references to the same element of the String constant pool (approximately, “memory cell”). You used reflection to modify this element; therefore, all the "Mario" in your code is now as if you wrote "Luigi" .

+24
Sep 17 '15 at 6:35
source share

To explain the existing answers a bit more, let's take a look at your generated bytecode (only here main() ).

Bytecode

Now, any changes to the contents of this location will affect both the links (and any others that you give too).

+16
Sep 17 '15 at 6:41
source share

String literals are stored in a string pool and their canonical values ​​are used. Both "Mario" literals are not just strings with the same value, they are the same object . Manipulating one of them (using reflection) will change them “both,” since they are just two references to the same object.

+9
Sep 17 '15 at 6:35
source share

You just changed the String Mario constant pool of String constants to Luigi , referenced by several String s, so every Mario link literal is now Luigi .

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

You have selected the char[] named value field from the String class

 stringValue.setAccessible(true); 

Make available.

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

You changed the original String field to Luigi . But the original Mario literal is String and the literal belongs to the String pool, and they are all interned. This means that all literals that have the same content refer to the same memory address.

 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. 

You basically changed the Mario pool from String , which is reflected in all the links. If you create a String Object (i.e. new String("Mario") ) instead of a literal, you will not encounter this behavior because you will have two different Mario .

+8
Sep 17 '15 at 6:50
source share

Other answers adequately explain what is happening. I just wanted to add that this only works if a security manager is not installed. When you run code from the command line, there is no default, and you can do such things. However, in an environment where trusted code is mixed with untrusted code, such as an application server in a production environment or an isolated sandbox in a browser, there is usually a security manager, and you will not be allowed to use these types of frauds, so this is probably a less scary security hole.

+5
Sep 17 '15 at 10:33
source share

Another related point: you can use a constant pool to improve the performance of string comparisons in some cases using String.intern() .

This method returns an instance of String with the same contents as String, from which it is called from the String constant pool, adding it if it is not already present. In other words, after using intern() all strings with the same content are guaranteed to be the same String instance as the other string constants with this content, that is, you can use the equals ( == ) operator on them.

This is just an example that is not very useful in itself, but it illustrates the 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"; } 

This little trick is not worth developing your code, but it is worth remembering the day when you notice that it can be extracted a bit faster from some bit of performance-sensitive code using the == operator on the line with reasonable use of intern() .

+3
Sep 18 '15 at 12:48
source share



All Articles