这个Java代码片段如何工作?(字符串池和反射)

mac*_*rog 84 java string reflection string-pool

Java字符串池加上反射可以在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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

上面的代码将打印:

"Luigi Luigi" 
Run Code Online (Sandbox Code Playgroud)

马里奥怎么了?

Jon*_*eet 96

马里奥怎么了?

你基本上改变了它.是的,通过反射你可以违反字符串的不变性......并且由于字符串实习,这意味着任何使用"Mario"(除了在更大的字符串常量表达式中,这将在编译时解析)将结束在程序的其余部分中作为"Luigi".

这种事情是为什么反射需要安全权限...

需要注意的是表达str + " " + "Mario"没有执行任何编译时的串联,由于左结合+.这是有效的(str + " ") + "Mario",这就是你仍然看到的原因Luigi Luigi.如果您将代码更改为:

System.out.println(str + (" " + "Mario"));
Run Code Online (Sandbox Code Playgroud)

...然后你会看到Luigi Mario编译器将实际" Mario"插入到不同的字符串中"Mario".

  • @ChrisHayes:不,由于`+`的关联性,这不是编译时常量表达式.它的评价为"(str +"")+"Mario".如果只打印"""+ Mario`或"""+"Mario"+ str`*然后*你有编译时连接,你仍然在输出中得到`Mario`. (7认同)
  • @ChrisHayes:在答案中添加了更多解释,因为它通常很有用. (3认同)

Ama*_*dan 24

它被设定为Luigi.Java中的字符串是不可变的; 因此,编译器可以将所有提及解释"Mario"为对同一个String常量池项的引用(粗略地说,"内存位置").您使用反射来更改该项目; 所以"Mario"你的代码中的所有内容现在都像你写的一样"Luigi".


Cod*_*der 16

为了更多地解释现有的答案,让我们看看你生成的字节代码(main()这里只有方法).

字节代码

现在,对该位置内容的任何更改都将影响这两个引用(以及您提供的任何其他内容).


Mur*_*nik 9

字符串文字存储在字符串池中,并使用它们的规范值.两个"Mario"文字不仅仅是具有相同值的字符串,它们是同一个对象.操纵其中一个(使用反射)将修改它们的"两者",因为它们只是对同一对象的两个引用.


Cod*_*roc 8

你只是改变了String字符串常量池 MarioLuigi这是由多个引用StringS,所以每一个引用的文字 Mario是现在Luigi.

Field stringValue = String.class.getDeclaredField("value");
Run Code Online (Sandbox Code Playgroud)

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

stringValue.setAccessible(true);
Run Code Online (Sandbox Code Playgroud)

让它可访问.

stringValue.set(original, "Luigi".toCharArray());
Run Code Online (Sandbox Code Playgroud)

你改变了original String 字段Luigi.但原来MarioString 文字和文字所属的String游泳池和所有被拘留.这意味着具有相同内容的所有文字都指向相同的内存地址.

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.
Run Code Online (Sandbox Code Playgroud)

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


Pep*_*itz 5

其他答案充分解释了正在发生的事情.我只是想补充一点,这只有在没有安装安全管理器的情况下才有效.默认情况下,从命令行运行代码时没有,你可以这样做.但是,在受信任的代码与不受信任的代码混合的环境中,例如生产环境中的应用程序服务器或浏览器中的applet沙箱,通常会有一个安全管理器存在,您不会被允许使用这些类型的恶作剧,所以这似乎不是一个可怕的安全漏洞.