什么是不可变对象?
如果一个对象,在它创建完成之后,不能再改变它的状态,那么这个对象就是不可变的。不能改变状态的意思是,不能改变对象内的成员变量,包括基本数据类型的值不能改变,引用类型的变量不能指向其他的对象,引用类型指向的对象的状态也不能改变。
String对象为什么不可变吗?
JDK1.7中String类的源码:
public final class String implements Serializable, Comparable<String>, CharSequence {
private final char[] value;
private int hash;
由以上的代码可以看出, 在Java中String类其实就是对字符数组的封装,在JDK7中,只有一个value变量,也就是value中的所有字符都是属于String这个对象的。 除此之外还有一个hash成员变量,是该String对象的哈希值的缓存。value也只是一个引用,它指向一个真正的数组对象。value这个变量是private的,并且没有提供setValue公共方法来修改这些值,所以在String类的外部无法修改String。也就是说一旦初始化就不能修改, 并且在String类的外部不能访问这个成员。此外,value变量是final的, 也就是说在String类内部,一旦这个值初始化了, 也不能被改变。所以可以认为String对象是不可变的了。
String对象真的不可变吗?
从上文可知String的成员变量是private final 的,也就是初始化之后不可改变。那么在这几个成员中, value比较特殊,因为他是一个引用变量,而不是真正的对象。value是final修饰的,也就是说final不能再指向其他数组对象,那么我能改变value指向的数组吗? 比如将数组中的某个位置上的字符变为下划线“_”。 至少在我们自己写的普通代码中不能够做到,因为我们根本不能够访问到这个value引用,更不能通过这个引用去修改数组。
我们知道,用反射可以访问私有成员, 可以反射出String对象中的value属性, 进而改变通过获得的value引用改变数组的结构。下面是实例代码:
public class MyClass {
public static void main(String[] args) {
try {
String str = "Owen Chan";
System.out.println("str: " + str); //Owen Chan
//获取String类中的value字段
Field valueField = String.class.getDeclaredField("value");
//改变value属性的访问权限
valueField.setAccessible(true);
//获取s对象上的value属性的值
char[] chars = (char[]) valueField.get(str);
//改变value所引用的数组中的第4个字符
chars[4] = '_';
System.out.println("str: " + str);
} catch (Exception e) {
e.printStackTrace();
}
}
}
打印结果:
str: Owen Chan
str: Owen_Chan
在这个过程中,str 始终引用的同一个String对象,但是再反射前后,这个String对象发生了变化, 也就是说,通过反射是可以修改所谓的“不可变”对象的。这个反射的实例还可以说明一个问题:如果一个对象,他组合的其他对象的状态是可以改变的,那么这个对象很可能不是不可变对象。例如一个Car对象,它组合了一个Wheel对象,虽然这个Wheel对象声明成了private final 的,但是这个Wheel对象内部的状态可以改变, 那么就不能很好的保证Car对象不可变。
private final static int[] array = new int[] {1, 3, 3};
public static void main(String[] args) {
array[1] = 4;
System.out.println("array[1]:" + array[1]);
}
array[1]:4
问题:
public static void main(String[] args) {
try {
String str1 = "hello world";
String str2 = "hello world";
String strNew = new String("hello world");
System.out.println("str1 == str2 : "+ (str1 == str2));
System.out.println("str1 == strNew : "+ (str1 == strNew));
System.out.println("str2 == strNew : "+ (str2 == strNew));
//修改hello 的value
Field hello_field = String.class.getDeclaredField("value");
hello_field.setAccessible(true);
char[] value=(char[])hello_field.get(str1);
value[5]='_';
System.out.println("str1: " + str1);
System.out.println("str2: " + str2);
System.out.println("strNew: " + strNew);
} catch (Exception e) {
e.printStackTrace();
}
}
打印结果:
str1 == str2 : true
str1 == strNew : false
str2 == strNew : false
str1: hello_world
str2: hello_world
strNew: hello_world
分析:
String str1=”hello world”; 和str2都是在常量池里,指向同一个对象。所以str和str2是同一个对象,new String这个是在堆里。strNew与其他两个不是同一个对象。关于后面反射机制修改了str1的值,strNew和str2的值也同步修改了。不管修改哪一个都会全部修改,所以应该可能是new的时候,查找了常量池是否存在这个数据,如果存在new出来是一个指向指针的指针