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
}
}
}
以上代码将打印:
"Luigi Luigi"
马里奥怎么了?
答案 0 :(得分:95)
马里奥怎么了?
你基本上改变了它。是的,通过反射你可以违反字符串的不变性......并且由于字符串实习,这意味着任何使用“Mario”(除了在更大的字符串常量表达式中,这将在编译时解析)将结束在该计划的其余部分中作为“Luigi”。
这种事情就是为什么反射需要安全权限......
请注意,由于str + " " + "Mario"
的左关联性,表达式+
执行而不是执行任何编译时串联。它实际上是(str + " ") + "Mario"
,这就是您仍然看到Luigi Luigi
的原因。如果您将代码更改为:
System.out.println(str + (" " + "Mario"));
...然后您会看到Luigi Mario
,因为编译器会将" Mario"
插入到"Mario"
的不同字符串。
答案 1 :(得分:24)
它被设定为Luigi。 Java中的字符串是不可变的;因此,编译器可以将"Mario"
的所有提及解释为对同一个String常量池项的引用(粗略地说,"内存位置")。您使用反射来更改该项目;因此,代码中的所有"Mario"
现在就像您编写"Luigi"
一样。
答案 2 :(得分:16)
答案 3 :(得分:9)
字符串文字存储在字符串池中,并使用它们的规范值。 "Mario"
个文字都不是具有相同值的字符串,而是相同的对象。操纵其中一个(使用反射)将修改"两者"其中,因为它们只是对同一个对象的两个引用。
答案 4 :(得分:8)
您刚刚将 字符串常量池 String
的{{1}}更改为多个{{1}引用的Mario
} s,所以每个引用文字 Luigi
现在都是String
。
Mario
您已从班级Luigi
Field stringValue = String.class.getDeclaredField("value");
名为char[]
字段的字段
value
让它可以访问。
String
您已将stringValue.setAccessible(true);
stringValue.set(original, "Luigi".toCharArray());
字段更改为original
。但原作是String
Luigi
文字,文字属于Mario
池,所有实习。这意味着具有相同内容的所有文字都指向相同的内存地址。
String
基本上,您已更改了所有引用字段中反映的String
池的Mario。如果您创建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
(即String
)而非文字,则不会遇到此行为,因为您将拥有两个不同的Object
。
答案 5 :(得分:5)
其他答案充分解释了正在发生的事情。我只是想补充一点,即只有在没有安装security manager的情况下才有效。默认情况下,从命令行运行代码时没有,你可以这样做。但是,在受信任的代码与不受信任的代码混合的环境中,例如生产环境中的应用程序服务器或浏览器中的applet沙箱,通常会有一个安全管理器存在,您不会被允许使用这些类型的恶作剧,所以这似乎不是一个可怕的安全漏洞。
答案 6 :(得分:3)
另一个相关点:在某些情况下,您可以使用常量池来提高字符串比较的性能,方法是使用String.intern()
方法。
该方法返回String的实例,其内容与从String常量池调用它的String相同,如果尚不存在,则将其添加。换句话说,在使用intern()
之后,所有具有相同内容的字符串都保证与彼此相同的String实例以及具有这些内容的任何String常量,这意味着您可以使用equals运算符({{1在他们身上。
这只是一个单独的例子,但它说明了一点:
==
这个小技巧并不值得设计你的代码,但值得注意的是,当你注意到使用{{{{{{{{{{{{{{ 1}}运算符在一个字符串上,明智地使用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";
}
。