String源码分析--Java中的String为什么不能改变

什么是不可变对象:

大家都知道java中String对象是不可变的,现在明确一个概念什么叫不可变对象。

一个对象在创建成功之后,对象中的成员变量不可被改变,基本类型的成员值不可改变,引用类型的成员变量不能指向不可改变


区分Java中对象和对象引用

下面用String来举例说明

String a = "abc"

System.out.println(a)

a = "abcABC"

因为a 只是指向String类型变量的一个引用,上面的代码只是将变量a由指向"abc"这个String对象转向"abcABC"这个对象

内存结构图如下:



      Java和C++的一个不同点是, 在Java中不可能直接操作对象本身,所有的对象都由一个引用指向,必须通过这个引用才能访问对象本身,包括获取成员变量的值,改变对象的成员变量,调用对象的方法等。而在C++中存在引用,对象和指针三个东西,这三个东西都可以访问对象。其实,Java中的引用和C++中的指针在概念上是相似的,他们都是存放的对象在内存中的地址值,只是在Java中,引用丧失了部分灵活性,比如Java中的引用不能像C++中的指针那样进行加减运算。

为什么Java中的String是不可变的

我们从两个方面来看,源码的设计和这样设计的优点

1:源码分析

上面是JDK8的String成员对象,字符数组,int类型的hash值,long类型的serialVersionUID都是provate类型的,并且没有提供get set方法,并且value[] 和 serialVersionUID是final类型的,对于外界这些字段的值都是没发改变的。在Java中数组也是对象,所以上图中的value是指向字符数组的一个引用。

在执行完 String s = "abc"后内存布局应该是这样的


进一步验证String对象是不可变的

public String substring(int beginIndex, int endIndex) {
        if (beginIndex < 0) {
            throw new StringIndexOutOfBoundsException(beginIndex);
        }
        if (endIndex > value.length) {
            throw new StringIndexOutOfBoundsException(endIndex);
        }
        int subLen = endIndex - beginIndex;
        if (subLen < 0) {
            throw new StringIndexOutOfBoundsException(subLen);
        }
        return ((beginIndex == 0) && (endIndex == value.length)) ? this
                : new String(value, beginIndex, subLen);
    }
上面是String中的subString方法

注意到最后标红的subString返回的新被截取的字符串都是新new出来的,

String s = "abc"
s = s.subString(0,1)

也就是上面代码中"abc"字符串对象并没有被改变,subString方法返回的是一个新的String对象然后让s指向该新对象,大家可以去验证,String类中其他类似的改变字符串的方法都是返回一个新的对象


2:设计String类为不可变的优点

1)出于安全考虑举例说明

  1. HashSet<StringBuilder> hs = new HashSet<StringBuilder>();  
  2.         StringBuilder sb1 = new StringBuilder("aaa");  
  3.         StringBuilder sb2 = new StringBuilder("aaabbb");  
  4.         hs.add(sb1);  
  5.         hs.add(sb2);    //这时候HashSet里是{"aaa","aaabbb"}  
  6.           
  7.         StringBuilder sb3 = sb1;  
  8.         sb3.append("bbb");//这时候HashSet里是{"aaabbb","aaabbb"}  
  9.         System.out.println(hs);  

        上面的代码中:将sb1,sb2添加到hashset中,然后修改sb1的值跟sb2的值相同,违反了Hashset中Key不能重复的原则

A:还有一点,在多线程的环境中,读是不会出现安全问题的,但竞争写操作会引起线程安全问题,不可变类型的变量不能被写所以线程安全。

B:在java堆内存中有常量池,常用的字符串值是直接存放在常量池中的,如果两个字符串的值相同其实是指向了常量池中的同一个变量,这样设计可以节省内存空间。

String one  = "someString";

String two = "someString"

String对象 的不可变性是常量池的基础,不然也就没了意义。




猜你喜欢

转载自blog.csdn.net/never_compromise2580/article/details/80393493