这个Java代码片段如何工作? (string池和reflection)

与reflection结合的Javastring池可以在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 } } } 

以上代码将打印:

 "Luigi Luigi" 

马里奥怎么了?

马里奥怎么了?

你基本上改变了它。 是的,通过reflection,你可以违反string的不变性……而且由于string实际上,这意味着任何对“马里奥”的使用(除了在编译时已经解决的更大的string常量expression式之外)将会结束在其余的节目中作为“路易”。

这种事情是为什么reflection需要安全权限…

请注意,由于+的左关联性,expression式str + " " + "Mario"不执行任何编译时间连接。 这是有效的(str + " ") + "Mario" ,这就是为什么你仍然看到Luigi Luigi 。 如果您将代码更改为:

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

…然后你会看到Luigi Mario因为编译器会将" Mario"插入到"Mario"的不同string中。

它被设置为路易吉。 Java中的string是不可改变的。 因此,编译器可以将所有提到的"Mario"解释为对同一个String常量池项(大致为“内存位置”)的引用。 你用reflection来改变那个项目; 所以你的代码中的所有"Mario"现在好像你写了"Luigi"

为了解释一下现有的答案,让我们来看看你生成的字节码(这里只有main()方法)。

字节码

现在,对该位置的内容所做的任何更改都会影响到这两个引用 (以及其他任何您也提供的内容)。

string文字存储在string池中,并使用它们的规范值。 两个"Mario"文字不只是具有相同的值的string,他们是同一个对象 。 操纵其中的一个(使用reflection)将修改它们的“两个”,因为它们只是对同一个对象的两个引用。

你只是把string常量池 Mario改成了由多个String引用的Luigi ,所以每一个引用文字 Mario现在都是Luigi

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

您已从类String获取char[]命名value字段

 stringValue.setAccessible(true); 

使其可访问。

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

您将original String字段更改为Luigi 。 但原来是MarioString 文字和文字属于String池,都是interned 。 这意味着所有具有相同内容的文字都指向相同的内存地址。

 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. 

基本上你已经改变了所有引用字段中反映String池的Mario。 如果你创build的String Object (即new String("Mario") )而不是文字你不会面对这种行为,因为你会有两个不同的Mario s。

其他答案充分解释发生了什么事情。 我只是想补充一点,只有在没有安装安全pipe理器的情况下才能使用。 当从命令行默认运行代码的时候,没有,你可以做这样的事情。 但是,在可信代码与不可信代码混合使用的环境中,例如生产环境中的应用程序服务器或浏览器中的小应用程序沙箱,通常会有一个安全pipe理器存在,您将不会被允许使用这种恶意代码。这似乎不是一个可怕的安全漏洞。

另一个相关点:通过使用String.intern()方法,您可以利用常量池来提高string比较在某些情况下的性能。

该方法返回String的实例,其内容与从String常量池中调用的String相同,如果不存在,则将其添加。 换句话说,在使用intern() ,具有相同内容的所有string保证为相同的String实例,并且与这些内容中的任何string常量一样,也就是说,您可以使用它们的equals运算符( == ) 。

这仅仅是一个自己并不是很有用的例子,但是它说明了这一点:

 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"; } 

这个小技巧是不值得devise你的代码的,但值得注意的是,当你注意到通过在string上使用==运算符,一些性能敏感的代码可以提供更多的速度明智地使用intern()