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

Java 使用反射修改字符串的目的是什么?,java,string,reflection,immutability,Java,String,Reflection,Immutability,我在读一篇文章说Java字符串不是完全不变的。但是,在本文修改字符串的示例代码中,它调用string.toUpperCase().toCharArray(),返回一个新字符串。那么,如果调用toUpperCase(),那么更改字符串的过程的目的是什么?代码如下: public static void toUpperCase(String orig) { try { Field stringValue = String.class.getDeclaredField("value");

我在读一篇文章说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()。这有什么原因吗?

不要在家里尝试!
您正在破坏字符串的不变性。没有充分的理由这样做。

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

您在上面定义的函数可以在位编辑原始字符串对象的内容。

1。)阅读
Bohemian
answer

2.)字符串在内部存储在字符数组中,因此需要调用
toCharArray
来设置字段。

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

他为什么这么做: 为了说明您可以将任意字符数组放在那里

为什么这是有用的: 字符串是不可变的,这允许您规避不可变性。当然,不建议这样做-永远不。相反,如果他说“小心,因为人们可能会对你的代码这样做。即使你认为你的东西是安全的,也可能不是!”

其影响是广泛的。不可变变量不再是不可变的。最终变量不再是最终变量。线程安全对象不再是线程安全的。你认为你可以依赖的合同,你不能再这样做了。所有这些都是因为某个工程师在某个地方遇到了一个他无法用常规方法解决的问题,所以他深入思考来解决它不要成为“那个家伙”。

您还将注意到,该字符串的哈希代码现在将如何更改。因此,如果您从未计算过该字符串的哈希代码,它仍然是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));
    }
        
}
import java.util.*;
导入java.lang.reflect.*;
类HashTest{
/**结果:
C:\Documents and Settings\glowcoder\My Documents>java HashTest
原始哈希:-804322678
新值:STACKOVERFLOW
包含源代码:true
包含副本:false
*/
公共静态void main(字符串[]args)引发异常{
Set=newhashset();
String str=“StackOverflow”;
System.out.println(“原始哈希:+str.hashCode());
set.add(str);
Field stringValue=String.class.getDeclaredField(“值”);
stringValue.setAccessible(true);
stringValue.set(str,str.toUpperCase().toCharArray());//
System.out.println(“新值:+str”);
字符串复制=新字符串(str);//强制复制
System.out.println(“包含源代码:+set.Contains(str));
System.out.println(“包含副本:“+set.Contains(copy));
}
}
我敢打赌,他这样做是为了警告人们不要有不良行为,而不是耍“酷”把戏

编辑:我找到了你提到的那篇文章,它是基于这篇文章的。原始文章指出:“这意味着,如果另一个包中的类“摆弄”了一个插入的字符串,它可能会对您的程序造成严重破坏。这是一件好事吗?(您不需要回答;-)”我认为这很清楚,这更像是一个保护指南,而不是关于如何编码的建议


因此,如果你只带着一条信息离开这个线程,那就是反射是危险的、不可靠的,不可玩弄

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

如果字段是公共的,即没有反射,则此方法的外观如下:

public static void toUpperCase(String orig) {
    orig.value = orig.toUpperCase().toCharArray();
}
由于
的类型为
char[]
,因此无法将
字符串
分配给此字段-这就是为什么需要在
.toUpperCase()之后调用
toCharray
的原因。如果您尝试这样做,您将得到一个异常(我想
ClassCastException
),但是那里的try-catch块会将其吃掉。(这给了我们另一个教训:永远不要使用这样的空捕捉块。)

注意:由于原始字符串的实际数据可能不会从
char[]
的开头开始,因此此代码可能不会执行正确的操作。由于不更新
偏移量
字段,因此在使用修改后的字符串时,将获得
indexootfboundseceptions
。此外,字符串对象缓存其哈希代码,因此这也将是错误的

下面是一个正确的方法:

public static void toUpperCase(String orig) {
    orig.value = orig.toUpperCase().toCharArray();
    orig.offset = 0;
    orig.hash = 0; // will be recalculated on next `.hashCode()` call.
}
通过反射,它看起来像这样:

public static void toUpperCase(String orig)
{
  try
  {
    Field stringValue = String.class.getDeclaredField("value");
    stringValue.setAccessible(true);
    stringValue.set(orig, orig.toUpperCase().toCharArray());
    Field stringOffset = String.class.getDeclaredField("offset");
    stringOffset.setAccessible(true);
    stringOffset.setInt(orig, 0);
    Field stringHash = String.class.getDeclaredField("hash");
    stringHash.setAccessible(true);
    stringHash.setInt(orig, 0);
  }
  catch (Exception ex){
     // at least print the output
     ex.printStackTrace();
  }
}

我认为我无法补充已经提供的解释,因此,也许我可以通过建议如何防止这种情况来补充讨论

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

public static void main(String args[]){

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

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

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

您使用反射更改了最后一个字符串以进行测试。有时,该字符串包含生产环境中使用但不适合测试的默认位置的路径。然而,该变量被多个对象/方法引用