大家都清楚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对象,用来进行比对的话,会发现一个有趣的现象。先看代码及注释中的运行结果:
String str = "abc";
System.out.println(str); //打印abc 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); //打印123 String str1 = "123";
String str2 = "abc"; System.out.println(str1 == str); //打印false
System.out.println(str2 == str); //打印true System.out.println(str1.equals(str)); //打印true
System.out.println(str2.equals(str)); //打印true
System.out.println(str2.equals(str1)); //打印true System.out.println(str); //打印123
System.out.println(str1); //打印123
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。