做Java开发的都知道,String具有不可变性,这个不可变性,怎么理解?因为用String时大家都有一个常识性的认识,那就是String初始化后,我是可以对其重新赋值的,比如:
String str1 = "123";
str1 = "1234";
明明是可以“变”的啊,什么叫不可变?下面我们来分析一下:
一、首先我们来分析下什么是String的不可变性
我们来看看String类的源代码:
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
从源码中我们可以看到3个信息:
1、String 类是final的,其不可被继承
2、value[]数组是私有的,且是final的,其引用指向的对象不可被修改
3、String类的方法中,没有任何方法对value[]数组做修改
以上3个信息,能得出以下结论:
String对象一旦被初始化,其value[]数组的值就不会被修改了,没有子类,也没有成员方法可以修改其值。这就是String对象的不可变性。
二、不可变性的作用
不可变性保证了String对象的线程安全,所以在高并发多线程情况下可以放心使用String对象。
三、对String值“可变”这一常识的解释
有人问了,那这段代码代表什么?
String str1 = "123";
str1 = "1234";
我们用一代测试代码来分析一下:
System.out.println("***************************************");
String str1 = "123";
Field field = String.class.getDeclaredField("value");
field.setAccessible(true);
char[] value = (char[]) field.get(str1);
value[0] = '0';
System.out.println("str1========>>>>"+str1);
String str2 = "123";
System.out.println("str2========>>>>"+str2);
String str3="023";
System.out.println("023"==str1);
System.out.println("023".equals(str1));
System.out.println("123" == str1);
System.out.println("123".equals(str1));
System.out.println("***************************************");
运行结果是:
***************************************
str1========>>>>023
str2========>>>>023
false
true
true
true
***************************************
这是不是很违反我们的常识?
1、str1已经被重新赋值为“023”,而str2是被赋值为“123"而且没有被改过,为什么str2的值是023?
2、str3被初始化为"023”,为什么用“==str1”得到的是false,用“equals(str1)"得到的是true?
3、str1的值不是被重新赋值为”023“了吗,为什么”"123" == str1”和“"123".equals(str1)”都是true?
我们现在来分析下为什么会是这样的结果:
1、先来看“==”号和“equals"在String比较中的含义有何不同:
我们都知道”==”号在进行String类型比较时,比较的是引用指向的对象的地址,而equals方法做了什么呢,我们来看源码:
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
可以看到,equals方法先是比较对象地址,然后再比较对象的value值。通过前文中我们的分析,可以知道,String对象一旦被初始化,其对象的地址是不会被改变的,所以,通过==号来比较,如果相同,则一定是同一个对象。但是用equals方法不同,equals方法先对对象地址比较,如果地址不同,会再进行值的比较。
那我们再对前文中的测试代码进行分析,就不难知道为什么看上去会出现一些违反我们常识的结果了。
String str1="123";
String str2="123";
这其中str1和str2其实只是2个引用指向了同一个对象(Java是怎么让String的多个引用指向同一个对象地址,后面再讲)。所以:
1、当我们用反射修改了str1指向的对象的值为“023”,那当我们想输出str2指向的对象的值时,当然显示的也是“023",而不是我们常识性地认为是初始化的”123“。
2、当我们明明修改了str1对象的值为"023”,而我们不管用”123“==str1还是“123”equals(str1),得到的还是true,是因为==号和equals都是先判断两个对象地址是否相同,当地址相同了,即便value值被修改了,得到的还是true的结果。
而下面这句:
String str3="023",则是创建了一个和”123“不同的对象,str3指向的对象的地址和str1、str2是不同的。所以:
1、当我们用==号比较str1和str3时,得到的是false的结果,因为这2个引用指向的对象地址是不一样的。
2、当我们用str3.equals(str1)时,得到的是true的结果,是因为这2个引用指向对象的值是一样的。
四、总结
通过前面三部分的分析,我们可以得出以下结论:
1、String对象一旦被初始化,如果不通过反射,其对象地址和值是不可变的,是线程安全的。
2、String对象的引用被重新赋值时,如果值与初始化时的值不同,则其实是新创建了一个对象,引用指向的对象已经被改变。
3、如果想要比较2个String对象是否为同一个,用==才正确,用equals不一定得到正确的结果。
4、如果想要比较2个String对象的值,最好不要用==,因为==号不一定能反映对象真实的值,要用equals。