使用反射修改字符串的目的是什么?

时间:2011-08-01 22:19:28

标签: java string reflection immutability

我正在阅读article,它说Java字符串不是完全不可变的。但是,在文章修改字符串的示例代码中,它调用string.toUpperCase()。toCharArray(),它返回一个新字符串。那么,如果你调用toUpperCase(),那么通过更改字符串的过程是什么目的呢?这是代码:

public static void toUpperCase(String orig)
{
 try
 {
    Field stringValue = String.class.getDeclaredField("value");
    stringValue.setAccessible(true);
    stringValue.set(orig, orig.toUpperCase().toCharArray());
 }
 catch (Exception ex){}
}

另外,我注意到string.toUpperCase()本身不起作用。它需要是string.toUpperCase()。toCharArray()。有这个原因吗?

7 个答案:

答案 0 :(得分:12)

他在做什么:

他正在获取一些他知道正确长度的字符数组(例如String的大写版本)并将其作为String的后备数组。 (支持数组在String类中称为value。)

他为什么要这样做:

为了说明你可以在任何你想要的char数组。

为什么这很有用:

字符串是不可变的,这允许您规避不变性。当然,不推荐这样做 - 永远。相反,如果他说“请注意,因为人们可能会对你的代码执行此操作,我不会感到惊讶。即使你认为你的东西是安全的,也可能不是!”

这种影响涉及广泛。不可变变量不再是不可变的。最终变量不再是最终的。线程安全对象不再是线程安全的。您认为可以依赖的合同,您不能再这样做了。所有这些都是因为某个工程师在某个地方遇到了问题,他无法用正常手段修复,所以他深入研究解决问题。 不要'那个人'。

您还将注意到现在如何更改该String的hashCode。所以,如果你从未计算过该String的hashCode,它仍然为0,所以你没事。另一方面,如果你计算了它,当你把它放在HashMap或HashSet中时,它将不会被检索。

请考虑以下事项:

import java.util.*;
import java.lang.reflect.*;

class HashTest {        

    /** Results:
     C:\Documents and Settings\glowcoder\My Documents>java HashTest
        Orig hash: -804322678
        New value: STACKOVERFLOW
        Contains orig: true
        Contains copy: false
     */

    public static void main(String[] args) throws Exception {

        Set<String> set = new HashSet<String>();
        String str = "StackOverflow";
        System.out.println("Orig hash: " + str.hashCode());
        set.add(str);

        Field stringValue = String.class.getDeclaredField("value");
        stringValue.setAccessible(true);
        stringValue.set(str, str.toUpperCase().toCharArray()); // 

        System.out.println("New value: " + str);

        String copy = new String(str); // force a copy
        System.out.println("Contains orig: " + set.contains(str));
        System.out.println("Contains copy: " + set.contains(copy));
    }

}

我敢打赌他这样做是为了警告不良行为而不是表现出“酷”的伎俩。

编辑:我找到了你所指的文章,以及它所基于的文章。最初的文章指出:“这意味着如果另一个包中的类”摆弄“一个实习字符串,它会对你的程序造成严重破坏。这是件好事吗?(你不需要回答;-)”我我认为这很清楚这是一个保护指南,而不是如何编码的建议。

因此,如果你只用一条信息离开这个线程,那就是反射是危险的,不可靠的,而且不容小觑!

答案 1 :(得分:6)

不要在家尝试这个!

你正在颠覆String的不变性。 没有有充分理由这样做。

答案 2 :(得分:4)

目的是什么?我不确定,问那个写这个东西的人。你通常不应该这样做。 String是不可变的原因。

如果字段是公共的,即没有反射,这个方法将如何显示:

public static void toUpperCase(String orig) {
    orig.value = orig.toUpperCase().toCharArray();
}

由于value的类型为char[],因此您无法为此字段分配String - 这就是您需要在{{1}之后进行toCharArray调用的原因}。如果你尝试这样做会得到一个例外(我想.toUpperCase()),但那里的try-catch块会把它吃掉。 (这给我们带来了另一个教训:永远不要使用这样的空捕获块。)

注意:此代码可能无法正确执行,因为原始字符串的实际数据可能不会在ClassCastException的开头处开始。由于您未更新char[]字段,因此在使用此类已修改的字符串时,您将获得offset。此外,String对象缓存其hashCode,因此这也是错误的。

这将是一个正确的方法:

IndexOutOfBoundsExceptions

通过反思,它看起来像这样:

public static void toUpperCase(String orig) {
    orig.value = orig.toUpperCase().toCharArray();
    orig.offset = 0;
    orig.hash = 0; // will be recalculated on next `.hashCode()` call.
}

答案 3 :(得分:4)

我想我无法添加已经提供的解释,所以也许我可以通过建议如何防止这种情况来增加讨论。

您可以通过使用安全管理器来防止有人以这些和其他非预期的方式篡改您的代码。

public static void main(String args[]){

    System.setSecurityManager(new SecurityManager());
    String jedi1 = "jedi";

    toUpperCase(jedi1);
    System.out.println(jedi1);
} 

这将在toUpperCase方法中生成异常,前提是您未授予默认策略文件中所有代码库的所有权限。 (在您当前的代码中,您的异常目前被吞下)。

答案 4 :(得分:2)

1。)阅读Bohemian回答。

2。)字符串内部存储在char数组中,这就是你需要调用toCharArray来设置字段的原因。

答案 5 :(得分:1)

默认情况下,String.toUpperCase()保留原始字符串,同时返回一个新的字符串对象。

您在上面定义的函数,就地编辑原始字符串对象的内容。

答案 6 :(得分:1)

使用反射更改最终字符串以进行测试。有时,该字符串包含生产环境中使用但不适合测试的默认位置的路径。然而,在您的测试中,您的触发器中的几个对象/方法引用了该变量,因此在测试期间您可能希望将其设置为特定值。

正如其他人所说,这可能是你不想做的事情(经常/永远)。