java字符串常量池 - java字面量




此Java代码段如何工作?(字符串池和反射) (5)

马里奥怎么了?

基本上,您已更改它。 是的,通过反射,您可能会破坏字符串的不变性……并且由于字符串中间化,这意味着对“ Mario”的任何使用(除了在编译时已解析的较大的字符串常量表达式中)都会结束在该程序的其余部分中将其命名为“ Luigi”。

这就是为什么反射需要安全权限的原因...

注意,由于 + 的左关联性,表达式 str + " " + "Mario" 不执行任何编译时串联。 实际上是 (str + " ") + "Mario" ,这就是为什么您仍然看到 Luigi Luigi 。 如果将代码更改为:

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

...然后您会看到 Luigi Mario 因为编译器会将 " Mario" 插入到与 "Mario" 不同的字符串中。

这个问题已经在这里有了答案:

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" 

马里奥怎么了?


为了进一步说明现有的答案,让我们看一下您生成的字节码(此处仅 main() 方法)。

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


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

该方法返回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";
}

这个小技巧不值得设计代码,但值得一提的是,当您发现可以通过对 == 运算符在字符串上使用 == 运算符来提高一些性能敏感代码的速度时,明智地使用 intern()


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


您只需将 String常量池 Mario String 更改为 Luigi ,该 String 已被多个 String 引用,因此每个引用 文字 Mario 现在都是 Luigi

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

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

stringValue.setAccessible(true);

使它可访问。

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

您已将 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.

基本上,您已经更改了 String 池的Mario,该池已 反映 在所有引用字段中。 如果您创建 String Object (即 new String("Mario") )而不是文字,那么您将不会遇到此问题,因为您将拥有两个不同的 Mario





string-pool