有个仁兄在 StackOverflow 上发起了一个问题,是这么问的: html
众所周知Java中的String对象是不可变的,但咱们来看下面这段代码: java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
String s1 ="Hello World";
String s2 ="Hello World";
String s3 = s1.substring(6);
System.out.println(s1);// Hello World
System.out.println(s2);// Hello World
System.out.println(s3);// World
Field field = String.class.getDeclaredField("value");
field.setAccessible(true);
char[] value = (char[])field.get(s1);
value[6] ='J';
value[7] ='a';
value[8] ='v';
value[9] ='a';
value[10] ='!';
System.out.println(s1);// Hello Java!
System.out.println(s2);// Hello Java!
System.out.println(s3);// World
|
为何这段代码会是这样的运行结果?为何s1和s2的值被改变了,可是s3的值却没有? 数组
回答#1: 工具
String对象是不可变的,但这仅意味着你没法经过调用它的公有方法来改变它的值。 spa
上面的代码经过反射机制绕过了正常的API。采用这种方式,你还能够改变枚举的值,甚至能够改变Integer类型自动装箱时使用的查找表。 指针
在这里,因为s1和s2指向同一个内部的字符串对象,所以他们的值都被改变了。正如其余回答所述,这是由编译器实现的。 htm
s3没有被改变的缘由确实令我诧异,我过去一直认为s3和s1共享同一个value数组(在Java 7u6以前的版本中的确是这样)。然而,经过查看String类的源码,咱们能够看出子串对象的value数组是从原字符串对象中拷贝获得的(经过使用Arrays.copyOfRange(..)方法)。这是s3没有被改变的缘由。 对象
你能够安装一个SecurityManager来防止恶意代码执行这一类的操做。但须要注意的是,有些库的实现依赖于这种反射技巧(例如ORM工具,AOP库等)。 ci
我在回复的开头写道String对象并非真的不可变,只是“看上去不可变”。这可能会误导读者觉得String类的当前版本在访问限制方面有所疏忽,但事实上value数组使用了private和final修饰符。所以开发者须要注意:在Java中没法将数组声明为不可变的,即便使用了正确的访问修饰符,也不能将它暴露在类的外面。 开发
鉴于这个话题如此火热,向各位推荐一些进阶读物:2009年JavaZone会议上Heinz Kabutz发表的关于反射技术的疯狂演说,本文涵盖了反射操做中的常见问题,以及其余一些有关反射技术的内容。本文很是好,也很是疯狂。
本文揭示了为何反射技术在某些场景下很是实用,但在大多数状况下,你应该避免使用它。
回答#2:
在Java中,若是两个String类型的变量被初始化为同一个字符串,那么这两个变量将被赋值以相同的对象引用。这就是表达式“Test1==Test2”返回值为true的缘由。
1
2
3
|
StringTest1="Hello World";
StringTest2="Hello World";
System.out.println(test1==test2);// true
|
Test3是由substring()方法建立的一个新的String对象,它并无和Test1共享同一个value数组。(注:因为原做者笔误,下图中变量test1和test3的首字母并未大写,望读者注意)
咱们能够经过反射技术访问String对象,获取到value数组的指针:
1
2
|
Field field =String.class.getDeclaredField("value");
field.setAccessible(true);
|
改变这个value数组的值便可以改变全部持有该数组指针的String对象的值,所以Test1和Test2的值都发生了改变。但因为Test3是由substring()方法建立的一个新的String对象,它的值并无被改变。