3.为什么String被设计是不可变的?

171 阅读4分钟

为什么String被设计是不可变的?

String a="a";
a="b";

看似改变,其实新建了一个字符串"b",把a的引用指向"b",原来"a"保持不变

调用subString,replace也是吧应用指向心字符串,没有改变原有字符串内容

为什么?

看源码

public final class String
implements Java.io.Serializable, Comparable<String>, CharSequence {
private final char value[];
}

value是final修饰,除了构造方法之外没有任何其他方法会修改value数组内容,value权限是private,外部类也访问不到,所以最终value是不可变的

那么有没有这种情况:其他类继承String类,重写相关方法,就可以修改value值呢?这样不就可变了吗?

String类是final修饰,所以String类不可被继承,因此没有任何人可以通过拓展或覆盖行为破坏String类不可变性,这就是String具备不可变性原因

String不可变好处

为什么要设计成这样?4个好处

1.字符串常量池

可以使用字符串常量池,在 Java 中有字符串常量池的概念,比如两个字 符串变量的内容一样,那么就会指向同一个对象,而不需创建第二个同样内容的新对象,例如:

String s1 = "a";
String s2 = "a";

左边这两个引用都指向常量池中的同一个 “lagou”,正是因为这样的机制,再加上 String 在程序中的应用是如此广泛,我们就可以节省大量的内存空间。

如果想利用常量池这个特性,这就要求 String 必须具备不可变的性质,否则的话会出问题,我们来看下 面这个例子:

String s1 = "a";
String s2 = "a";
s1 = "b";
System.out.println(s2);

我们想一下,假设 String 对象是可变的,那么把 s1 指向的对象从小写的 “lagou” 修改为大写的 “LAGOU” 之后,s2 理应跟着变化,那么此时打印出来的 s2 也会是大写的:

这就和我们预期不符了,同样也就没办法实现字符串常量池的功能了,因为对象内容可能会不停变化, 没办法再实现复用了。假设这个小写的 “lagou” 对象已经被许多变量引用了,如果使用其中任何一个引 用更改了对象值,那么其他的引用指向的内容是不应该受到影响的。实际上,由于 String 具备不可变的 性质,所以上面的程序依然会打印出小写的“a”,不变性使得不同的字符串之间不会相互影响,符合 我们预期。

2.用作 HashMap 的 key

String 不可变的第二个好处就是它可以很方便地用作 HashMap (或者 HashSet) 的 key。通常建议 把不可变对象作为 HashMap 的 key,比如 String 就很合适作为 HashMap 的 key。 对于 key 来说,最重要的要求就是它是不可变的,这样我们才能利用它去检索存储在 HashMap 里面的 value。由于 HashMap 的工作原理是 Hash,也就是散列,所以需要对象始终拥有相同的 Hash 值才能 正常运行。如果 String 是可变的,这会带来很大的风险,因为一旦 String 对象里面的内容变了,那么 Hash 码自然就应该跟着变了,若再用这个 key 去查找的话,就找不回之前那个 value 了。

3.缓存HashCode

在 Java 中经常会用到字符串的 HashCode,在 String 类中有一个 hash 属性,代码如下:

image.png

这是一个成员变量,保存的是 String 对象的 HashCode。因为 String 是不可变的,所以对象一旦被创 建之后,HashCode 的值也就不可能变化了,我们就可以把 HashCode 缓存起来。这样的话,以后每 次想要用到 HashCode 的时候,不需要重新计算,直接返回缓存过的 hash 的值就可以了,因为它不会 变,这样可以提高效率,所以这就使得字符串非常适合用作 HashMap 的 key。

而对于其他的不具备不变性的普通类的对象而言,如果想要去获取它的 HashCode ,就必须每次都重 新算一遍,相比之下,效率就低了。

4.线程安全

String 不可变的第四个好处就是线程安全,因为具备不变性的对象一定是线程安全的,我们不需要对其 采取任何额外的措施,就可以天然保证线程安全。 由于 String 是不可变的,所以它就可以非常安全地被多个线程所共享,这对于多线程编程而言非常重要,避免了很多不必要的同步操作。

\