关于Java String 不可变性的分析

做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。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值