java中字符串“不可变性”的破坏,使用反射破坏final属性。以及涉及到字符串常量池的问题。

JAVA学习网 2018-09-18 00:54:01

  大家都清楚java中String类是不可变的,它的定义中包含final关键字。一旦被创建,值就不能被改变(引用是可以改变的)。

  但这种“不可变性”不是完全可靠的,可以通过反射机制破坏。参考一下代码:

String str = "abc";
System.out.println(str);

Field field = String.class.getDeclaredField("value");
field.setAccessible(true);

char[] value = (char[])field.get(str);
value[0] = '1';
value[1] = '2';
value[2] = '3';

System.out.println(str);

  这段程序会输出:

  abc

  123

  此处没有给str赋上新的引用值,说明字符串对象确实被改变了。但如果创建新的String对象,用来进行比对的话,会发现一个有趣的现象。先看代码及注释中的运行结果:

 1         String str = "abc";
 2         System.out.println(str);  //打印abc
 3         
 4         Field field = String.class.getDeclaredField("value");
 5         field.setAccessible(true);
 6         
 7         char[] value = (char[])field.get(str);
 8         value[0] = '1';
 9         value[1] = '2';
10         value[2] = '3';
11         System.out.println(str);  //打印123
12         
13         String str1 = "123";
14         String str2 = "abc";
15         
16         System.out.println(str1 == str);  //打印false
17         System.out.println(str2 == str);  //打印true
18         
19         System.out.println(str1.equals(str));  //打印true
20         System.out.println(str2.equals(str));  //打印true
21         System.out.println(str2.equals(str1));  //打印true
22         
23         System.out.println(str);  //打印123
24         System.out.println(str1);  //打印123
25         System.out.println(str2);  //打印123

  前两个打印仍然是原先的值就不说,后面几个输出信息就很有意思了。仔细想想其出现的原因,应该是因为有字符串常量池的存在。常量池相关的知识网上和各种java基础的书上有很多介绍,这里只简单说下:

  jvm在用户创建字符串的时候,会检查字符串常量池中该字符串(字面量,而不是引用)是否存在,若不存在,则将字符串放入常量池中,如果存在则直接返回该字符串的内存地址给String对象。

  现在问题就清楚了,str1和str2创建的时候,JVM没有在常量池中找到123这个字面量,找到了abc这个字面量(之前创建过),所以给str1分配了新的内存,而直接将abc的内存地址(虽然现在已经不是存储的abc这个字面量了),即str指向的地址直接返回给了str2。

  所以当用==比较的时候,str和str1指向不同的内存地址,返回了false,而str和str2则会返回true。

  后面的equals比较,只要清楚一点就能搞清楚原因了:为什么str2的值会是123呢?因为如上所诉,JVM在创建str2的时候,直接将abc这个内存的地址给到了str2,而这个内存块中现在存储的值已经被我们通过反射强行改成了123。

 

阅读(2473) 评论(0)