String为什么是不可变的

在介绍之前首先我们要知道什么 是不可变对象
如果一个对象,在它创建完成之后,不能再改变它的状态(指不能改变对象内的成员变量),那么这个对象就是不可变的。
首先我们来看一段代码:

public class Demo {
    String str = "ABC";
    System.out.println("s = " + str);
    str = "123";
    System.out.println("s = " + str);
}

执行结果:

s = ABC
s = 123

分析上述代码,str的值变了,那为啥说String对象是不可变的呢?

这里存在一个误区:str只是一个String对象的引用,并不是对象本身。对象在内存中是一块内存区,放在堆中,成员变量越多,这块内存区占的空间越大。引用只是一个4字节的数据,里面存放了它所指向的对象的地址,通过这个地址可以访问对象,而这个引用存放在Java虚拟机栈栈帧的局部变量表中。也就是说,str只是一个引用,它指向了一个具体的对象,当str=“123”; 这句代码执行过之后,又创建了一个新的对象“123”, 而引用str重新指向了这个新的对象,原来的对象“ABC”还在内存中存在,并没有改变。
我们用一张内存结构图来看看整个变化过程:
在这里插入图片描述
String对象是真的不可变吗?
从上文可知String的成员变量是private final 的,也就是初始化之后不可改变。那么在这几个成员中,value比较特殊,因为他是一个引用变量,而不是真正的对象。value是final修饰的,也就是说final不能再指向其他数组对象,那么我能改变value指向的数组吗?我们来看下面的代码:

final int[] value={1,2,3}
int[] another={4,5,6};
value=another; //编译器报错,final不可变

value用final修饰,编译器不允许我把value指向堆区另一个地址。但如果我直接对数组元素动手,分分钟搞定

final int[] value={1,2,3};
value[2]=100; //这时候数组里已经是{1,2,100}

所以String是不可变,关键是因为设计源代码的工程师,在后面所有String的方法里很小心的没有去动Array里的元素,没有暴露内部成员字段。private final char value[]这一句里,private的私有访问权限的作用都比final大。而且设计师还很小心地把整个String设成final禁止继承,避免被其他人继承后破坏。所以String是不可变的关键都在底层的实现,而不是一个final。

不可变有什么好处?
1、多线程下安全性
最简单地原因,就是为了安全。因为String是不可变的,因此多线程操作下,它是安全的,我们来看下面一段代码:

public String get(String str){
 str += "aaa";
 return str;
}

试想一下,如果String是可变的,那么get方法内部改变了str的值,方法外部str也会随之改变。
2、类加载中体现的安全性
类加载器要用到字符串,不可变提供了安全性,以便正确的类被加载,例如你想加载java.sql.Connection类,而这个值被改成了xxx.Connection,那么会对你的数据库造成不可知的破坏。
3、使用常量池可以节省空间
像下面这样字符串one和two都用字面量"something"赋值。它们其实都指向同一个内存地址

String one = "someString";
String two = "someString";

这样在大量使用字符串的情况下,可以节省内存空间,提高效率。但之所以能实现这个特性,String的不可变性是最基本的一个必要条件。要是内存里字符串内容能改来改去,这么做就完全没有意义了。

发布了30 篇原创文章 · 获赞 12 · 访问量 3467

猜你喜欢

转载自blog.csdn.net/zx1293406/article/details/103548393