使用反射更改字符串的效果

时间:2011-08-03 20:21:35

标签: java jvm immutability

众所周知,String在java中是不可变的。但是,可以通过获取Field并设置访问级别来使用反射来更改它。 (我知道这是未经修改的,我不打算这样做,这个问题纯粹是理论上的)。

我的问题:假设我知道自己在做什么(并根据需要修改所有字段),程序是否会正常运行?或者jvm是否进行了一些依赖于String不可变的优化?我会遭受性能损失吗?如果是这样,它会做出什么假设?该计划会出现什么问题

P.S。字符串只是一个例子,除了示例之外,我实际上对一般答案感兴趣。

谢谢!

7 个答案:

答案 0 :(得分:6)

编译后,某些字符串可能会引用一个实例,因此,您将编辑超出您想要的内容,并且永远不知道您还在编辑其他内容。

public static void main(String args[]) throws Exception {
    String s1 = "Hello"; // I want to edit it
    String s2 = "Hello"; // It may be anywhere and must not be edited
    Field f = String.class.getDeclaredField("value");
    f.setAccessible(true);
    f.set(s1, "Doesn't say hello".toCharArray());
    System.out.println(s2);
}

输出:

Doesn't say hello

答案 1 :(得分:2)

我想到的是字符串实习 - 文字,常量池中的任何内容以及任何手动intern() ed指向相同的字符串对象。如果你开始搞乱一个实习字符串文字的内容,你可能会看到使用相同底层对象的所有其他文字的完全相同的改动。

我不确定上面是否真的发生了,因为我从未尝试过(理论上它会,我不知道是否会在场景中发生某些事情来阻止它,但我怀疑它)但是就像那样可能会引发潜在的问题。当然,它也可能在Java级别上抛出问题,只需将多个引用传递给相同的字符串,然后使用反射攻击从其中一个引用中更改对象。大多数人(包括我!)都不会明确地防范代码中的那种事情,因此将攻击用于任何不属于您自己的代码,或者如果您还没有防范它们,则使用您自己的代码,可能导致各种类型奇怪的,可怕的错误。

从理论上讲这是一个有趣的领域,但你挖的越多,你就越明白为什么沿着这些方向做任何事都是个坏主意!

在字符串之外说,对于一个不可变的对象,没有我所知道的性能增强(事实上我不认为JVM现在甚至不能告诉对象是否是不可变的,抛开反射攻击。)它可能会抛出东西比如checker-framework关闭或任何试图静态分析代码以保证代码不可变的东西。

答案 2 :(得分:2)

如果你这样做,你肯定会遇到麻烦。这是否意味着你肯定会立即看到错误?不会。在很多情况下,你可能会放弃它,这取决于你正在做什么。

以下是一些会让你感到困惑的案例:

  • 修改在代码中某处声明为文字的字符串。例如,您有一个function,其某处被称为function("Bob");在这种情况下,字符串"Bob"在整个应用中更改为 (对于声明为final的字符串常量也是如此。)
  • 修改子字符串操作中使用的字符串,或者是子字符串操作的结果。在Java中,获取字符串的子字符串实际上使用与源字符串相同的基础字符数组,这意味着对源字符串的修改将影响子字符串(反之亦然)。
  • 您可以修改恰好在地图中用作键的字符串。它将不再与其原始值进行比较,因此查找将失败。

我知道这个问题是关于Java的,但我wrote a blog post回过头来说明如果你在.NET中改变一个字符串,你的程序可能会表现得多么疯狂。情况非常相似。

答案 3 :(得分:1)

我很确定JVM本身并不假设字符串的不变性,因为Java中的“不变性”不是语言级别的构造;它是类的实现所暗示的暗示的特征,但是,正如你所注意到的那样,它不能在存在反射的情况下得到保证。因此,它也与性能无关。

但是,几乎所有存在的Java代码(包括标准API实现)都依赖于字符串是不可变的,如果你打破了这种期望,你会看到各种各样的错误。

答案 4 :(得分:0)

String类中的私有字段是char [],偏移量和长度。更改任何这些不应对任何其他对象产生任何不利影响。但是如果你能以某种方式改变char []的内容,那么你可能会看到一些令人惊讶的副作用。

答案 5 :(得分:0)

public static void main(String args[]){
    String a = "test213";
    String s = new String("test213");
    try {
        System.out.println(s);
        System.out.println(a);
        char[] value = (char[])getFieldValue(s, "value");
        value[1] = 'a';
        System.out.println(s);
        System.out.println(a);

    } catch (Exception e) {
        e.printStackTrace();
    }
}

static Object getFieldValue(String s,String fieldName) throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
    Object chars = null;
    Field innerCharArray = String.class.getDeclaredField(fieldName);
    innerCharArray.setAccessible(true);
    chars = innerCharArray.get(s);
    return chars;
}

更改S的值将改变所有答案中提到的a的字面值。

答案 6 :(得分:0)

演示如何搞砸程序:

System.out.print("Initial: "); System.out.println(addr);
editIntStr("ADDR_PLACEH", "192.168.1.1");
System.out.print("From var: "); System.out.println(addr);//
System.out.print("Hardcoded: "); System.out.println("ADDR_PLACEH");
System.out.print("Substring: "); System.out.println("ADDR_PLACE" + "H".substring(0));
System.out.print("Equals test: "); System.out.println("ADDR_PLACEH".equals("192.168.1.1"));
System.out.print("Equals test with substring: ");  System.out.println(("ADDR_PLACE" + "H".substring(0)).equals("192.168.1.1"));

输出:

Initial: ADDR_PLACEH
From var: 192.168.1.1
Hardcoded: 192.168.1.1
Substring: ADDR_PLACEH
Equals test: true
Equals test with substring: false

第一次Equals测试的结果很奇怪,不是吗?你不能指望你的程序员们弄清楚为什么Java认为他们是平等的...
完整的测试代码:http://pastebin.com/vbstfWX1