我们可能了解到Java中的字符串为不可变对象,那么所谓的不可变对象又怎样理解呢?
不可变对象:我们姑且可以认为如果一个对象在创建完之后,不能够改变它的状态,那么这个对象就是不可变对象。那又怎么理解不可变呢?
不可变:我们可以认为不能够改变对象中的成员变量,基本类型的值不能改变,引用类型的变量不能再指向其他对象,引用类型指向的对象的状态不能改变。
分析:
我们可以看到Strng类被标识为final类型,也就是说不能被继承。从源码中可以看到jdk1.8中String的成员变量中的value很特殊,也被标识为final类型的。透过上面我们可以知道String类的底层其实是对字符数组的封装,而value就是封装的数组,并且查看String类中所有的方法,我们可以得知String类并没有提供公共的方法来修改这些值,所以我们从外部是无法修改String,我们可以理解为String一旦初始化便不可修改,并且外部不能够访问成员变量。【真的不能修改吗?可以想象反射】
代码清单1分析:
public static void main(String[] args) {
String value = "Hello World";
System.out.println("原值:" + value);
value = "hello world";
System.out.println("新值:" + value);
}
通过上面的代码,我们可以看到value的值变了,这是一种假象,那么这又怎么解释呢?我们都知道这里的value只是对象的引用而并不是对象本身,它指向了一个具体的对象,对象在内存中有一块内存区,它的成员变量越多,那这块内存区域就越大,而引用只是一个占了4个字节的数据,里面存放着它所指向的对象的地址,它通过这个地址可以访问到对象而当value = "hello world";又创建了一个新的对象,而引用value又指向了这个新的对象,原来的对象Hello World还在内存中,并没有改变。
代码清单2分析:
public static void main(String[] args) {
String value = "Hello World";
System.out.println("原值:" + value);
value = "hello world";
System.out.println("新值:" + value);
try {
Field valueField = String.class.getDeclaredField("value");
valueField.setAccessible(Boolean.TRUE);
try {
char[] original = (char[])valueField.get(value);
original[5] = '-';
System.out.println("反射改变后值:" + value);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
从代码清单2中,我们通过反射得到String的成员变量private final char[] value;也就是说初始化后不能改变,value【String中的value数组】比较特殊,因为它是一个引用变量,而不是真正的对象,被标识为final类型,不能再指向其他数组对象,如果我们要改变数组中某个位置的字符,在普通的代码中我们无法做到,因为我们无法从外部获得成员变量value的引用,更不能通过引用去修改它,但反射可以,在这个过程中,value始终引用的是同一个String对象,但是反射后String对象发生了变化也就是说通过反射可以修改不可变对象。
注:java与C++
java和C++唯一不同点是,java不能直接操纵对象本身,所有的对象都是由一个引用指向,必须通过这个引用才能访问到对象本身,包括成员变量的值,改变对象的成员变量,调用对象的方法
C++中存在引用,对象,指针对可以访问对象。
java中的引用和C++中的指针类似,他们都是存放的对象在内存中的地址,只是java中,引用丧失了部分的灵活性,
java中的引用不能像C++中的指针那样进行加减运算。