相信任何学习java的人,都会在书中看到“String字符串是不可变的,一旦创建就不能修改”这样的经典语句。也就是说写出String s = “aaa”; 之后再写 s = "bbb"; 还是没用,jvm会在内存中重新创建一个String对象“bbb”,而原来的“aaa”对象依然存在。 而且内存中“aaa”这个内容是不能修改的.这就是大多数人所接触的String以及对String 的解。
不过经过我最近的研究,发现利用反射还是可以修改String对象。
首先,研究String类源码,String类有一个 char 数组value,是final的,用来当作存储字符串的容器,也就是说String s=“aaa”;这个字符串真正是这么存储的: value[0]='a'; value[1]='a'; value[2]='a'; 而且value是final的,这就是说value在编译时就已经决定了。因此,这就是我们所说的String是不可变的。
不过,当这一切的一切遇上java无敌的反射机制时,就好象防备森严的公主闺房下竟然有一条直通大街的地道,任何通过这条地道的人都可以一窥公主闺房。所以,通过Java的反射就可以改变String对象的内容。
那么为什么反射就能改变String的内容呢?这是因为final是只对编译有效的,对运行无效。也就是说可以在运行是改变final的内容(当然前提是不能照着常规思路写,那样都不可能通过编译,怎么能运行呢?),所以你可以在运行时通过反射得到String的value的值,然后将新的值设置到value中,就改变了String对象。具体代码如下:
String s = "aaaa"; System.out.println(s); try{ Field field = s.getClass().getDeclaredField("value"); //String 类含有一个名为value的char数组,用于存储 field.setAccessible(true); if(null != field.get("value")){ System.out.println(field.get("value")); }else{ System.out.println("no data"); } field.set(s, new char[] {'b','b','b','b'}); System.out.println(s); }catch(NoSuchFieldException e){ e.printStackTrace(); }catch(SecurityException e){ e.printStackTrace(); }catch(IllegalAccessException e){ e.printStackTrace(); }
运行结果如下:
aaaa[C@1d057dabbbb
只是简简单单的一句替代,运行结果就会改变,结果如下:
aa
[C@1d057dabb看到了吗?即使是下面new了一个大小为4的char数组,仍然只有两个‘b’存入到了s的value中。这个好歹还没有抛出异常,要是替换成如下代码,更好的事情就来了,
String s = "aaaaaaa";
抛出异常如下:
aaaaaaa[C@1d057daException in thread "main" java.lang.ArrayIndexOutOfBoundsException at java.lang.System.arraycopy(Native Method) at java.lang.String.getChars(Unknown Source) at java.io.BufferedWriter.write(Unknown Source) at java.io.Writer.write(Unknown Source) at java.io.PrintStream.write(Unknown Source) at java.io.PrintStream.print(Unknown Source) at java.io.PrintStream.println(Unknown Source) at Test.main(Test.java:21)
呵呵,爽了吧。
由此可见,通过地道进入公主闺房毕竟不是见得阳光的事情,稍微有一点点不符合要求的都会被发现,只有当第一个char数组和第二个char数组的大小严格相等的时候才会出现我们期望的结果。
注:此文来自iteye飞檐走壁的博客,原文地址:http://james23dier.iteye.com/blog/626723,与大家分享