使用reflection修改字符串的目的是什么?
我正在阅读一篇文章 ,说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的后备数组。 (支持数组在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 set = new HashSet (); 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)); } }
我敢打赌他这样做是为了警告不良行为而不是表现出“酷”的伎俩。
编辑:我找到了你所指的文章,以及它所基于的文章。 最初的文章指出:“这意味着如果另一个包中的一个类”使用实习字符串“摆弄”,它可能会对你的程序造成严重破坏。这是件好事吗?(你不需要回答;-)“我我认为这很清楚这是一个保护指南,而不是如何编码的建议。
因此,如果你只用一条信息离开这个线程,那就是reflection是危险的,不可靠的,而且不容小觑!
不要在家尝试这个!
你正在颠覆String的不变性。 没有充分的理由这样做。
什么目的? 我不确定,问那个写这个东西的人。 你通常不应该这样做。 String是不可变的原因。
如果字段是公共的,即没有reflection,这个方法将如何显示:
public static void toUpperCase(String orig) { orig.value = orig.toUpperCase().toCharArray(); }
由于value
char[]
类型,因此无法为此字段分配String
– 这就是在.toUpperCase()
之后需要调用.toUpperCase()
。 如果你尝试这样做会得到一个exception(我想是ClassCastException
),但那里的try-catch块会把它吃掉。 (这给我们带来了另一个教训: 永远不要使用这种空的挡块 。)
注意:此代码可能无法正确执行,因为原始字符串的实际数据可能不会在char[]
开头处开始。 由于您不更新offset
字段,因此在使用此类已修改的String时将获得IndexOutOfBoundsExceptions
。 此外,String对象缓存其hashCode,因此这也是错误的。
这将是一个正确的方法:
public static void toUpperCase(String orig) { orig.value = orig.toUpperCase().toCharArray(); orig.offset = 0; orig.hash = 0; // will be recalculated on next `.hashCode()` call. }
通过reflection,它看起来像这样:
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方法中生成exception,前提是您未授予默认策略文件中所有代码库的所有权限。 (在您当前的代码中,您的exception当前被吞下)。
1.)阅读Bohemian
答案。
2.)字符串内部存储在char数组中,这就是你需要调用toCharArray
来设置字段的原因。
默认情况下,String.toUpperCase()保留原始字符串,同时返回新的字符串对象。
您在上面定义的函数,就地编辑原始字符串对象的内容。
您使用reflection更改最终字符串以进行测试。 有时,该字符串包含生产环境中使用但不适合测试的默认位置的路径。 但是,您的测试中的触发器会引用该变量,因此在测试期间您可能希望将其设置为特定值。
正如其他人所说,这可能是你不想做的事情(经常/永远)。