原创文章,转载请注明出处https://blog.csdn.net/kk123k/article/details/80752476
对于初学者很多人会疑惑Java中的String s = new String("hello")和String s = "hello"究竟有什么区别呢?
下面就来探讨一下,先看以下代码
String newS1 = new String("hello");
String newS2 = new String("hello");
String ss1 = "hello";
String ss2 = "hello";
System.out.println("ss1="+ss1);
System.out.println("ss2="+ss2);
System.out.println("newS1="+newS1);
System.out.println("newS2="+newS2);
System.out.println("ss1==ss2是"+(ss1==ss2));
System.out.println("ss1==newS1是"+(ss1==newS1));
System.out.println("newS1==newS2是"+(newS1==newS2));
由上结果可知,ss1和ss2其实是同一个对象;而newS1和newS2不是同一个对象;其实JVM内部是这样的
String newS1 = new String("hello")首先会在堆中创建一块内存, 内存地址返回给栈中newS1,然后因为"hello"是常量,JVM会去方法区中的字符串常量池查看是否有"hello"字符串的对象,没有的话就分配一个空间来存放"hello",并将其空间地址存入堆中new出来的对象中;
在新建newS2对象时先去方法区的字符串常量池中寻找"hello"字符串对象,发现已经有了,就直接将其内存地址存入堆中new出来的对象中,同理堆中new出来的对象的内存地址返回给newS2
对于String ss1="hello"这样创建的对象,JVM会直接检查字符串常量池是否已有"hello"字符串对象,如没有,就分配一个内存存放"hello",如有了,则直接将字符串常量池中的地址返回给栈中的ss1而不经过堆中new的对象
所以ss1和ss2指向的是同一个对象
这个时候有人就会有疑问了,既然ss1和ss2指向的是同一个对象,那我修过ss1的值岂不是也同时改变了ss2的值?
显然,改变ss1的值并不会影响ss2以及其它对象的值,原因其实Java的String是一个不可变的类,假设我们对ss1重新赋值时其实是在字符串常量池中重新分配了一块内存然后令ss1指向新对象
我们可以尝试用反射来强行修改字符串常量池中对象的值,来看看会怎样
String内部是由一个char[] value和int hash值构成,但是Java,下面用反射来改变其内部的value值
//获取String类中的value字段
Field valueFieldOfString = String.class.getDeclaredField("value");
//改变value属性的访问权限
valueFieldOfString.setAccessible(true);
//获取ss1对象上的value属性的值
char[] value = (char[]) valueFieldOfString.get(ss1);
//把value所引用的数组中的第5个字符为'e'
value[4] = 'e';
System.out.println("ss1="+ss1);
System.out.println("ss2="+ss2);
System.out.println("newS1="+newS1);
System.out.println("newS2="+newS2);
System.out.println("ss1==ss2是"+(ss1==ss2));
System.out.println("ss1==newS1是"+(ss1==newS1));
System.out.println("newS1==newS2是"+(newS1==newS2));
结果所有最终指向字符串常量池为"hello"的对象最终都会变成"helle"。
其内存原理如下:
用反射强行把方法区中的"hello"中的'o'改成了'e',所以最终指向同一个字符串常量池中的对象的值都会跟着改变