Java 中 String 类为什么要设计成不可变的?

String 是 Java 中不可变的类,所以一旦被实例化就无法修改。不可变类的实例一旦创建,其成员变量的值就不能被修改。本文总结下 String 类设计成不可变的原因及好处,以及 String 类是如何设计成不可变的。

String 类设计成不可变的原因及好处?

其实好处就是原因,String 设计成不可变,主要是从性能和安全两方面考虑。

1、常量池的需要

这个方面很好理解,Java 中的字符串常量池的存在就是为了性能优化。

字符串常量池(String pool)是 Java 堆内存中一个特殊的存储区域,当创建一个 String 对象时,假如此字符串已经存在于常量池中,则不会创建新的对象,而是直接引用已经存在的对象。这样做能够减少 JVM 的内存开销,提高效率。

String s1 = "abc";
String s2 = "abc";

比如引用 s1和 s2 都是指向常量池的同一个对象 "abc",如果 String 是可变类,引用 s1 对 String 对象的修改,会直接导致引用 s2 获取错误的值。

所以,如果字符串是可变的,那么常量池就没有存在的意义了。

2、hashcode 缓存的需要

因为字符串不可变,所以在它创建的时候 hashcode 就被缓存了,不需要重新计算。这就使得字符串很适合作为 HashMap 中的 key,效率大大提高。

3、多线程安全

多线程中,可变对象的值很可能被其他线程改变,造成不可预期的结果。而不可变的 String 可以自由在多个线程之间共享,不需要同步处理。

String 类是如何实现不可变的?

1、私有成员变量

String 的内部很简单,有两个私有成员变量

/** The value is used for character storage. */
private final char value[];

/** Cache the hash code for the string */
private int hash; // Default to 0

而并没有对外提供可以修改这两个属性的方法。

2、Public 的方法都是复制一份数据

String 有很多 public 方法,每个方法都将创建新的 String 对象,比如 substring 方法:

public String substring(int beginIndex) {
    if (beginIndex < 0) {
        throw new StringIndexOutOfBoundsException(beginIndex);
    }
    int subLen = value.length - beginIndex;
    if (subLen < 0) {
        throw new StringIndexOutOfBoundsException(subLen);
    }
    return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}

3、String 是 final 的

String 被 final 修饰,因此我们不可以继承 String,因此就不能通过继承来重写一些方法。

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
}

4、构造函数深拷贝

当传入可变数组 value[] 时,进行 copy 而不是直接将 value[] 复制给内部变量。

public String(char value[]) {
    this.value = Arrays.copyOf(value, value.length);
}

从 String 类的设计方式,我们可以总结出实现不可变类的方法:

  • 将 class 自身声明为 final,这样别人就不能通过扩展来绕过限制了。
  • 将所有成员变量定义为 private 和 final,并且不要实现 setter 方法。
  • 通过构造对象时,成员变量使用深拷贝来初始化,而不是直接赋值,这是一种防御措施,因为你无法确定输入对象不被其他人修改。
  • 如果确实需要 getter 方法,或者其他可能返回内部状态的方法,使用 copy-on-write 原则,创建私有的 copy。

原文发布于微信公众号 - 空帆船w(gh_a27e6529b76c)

原文发表时间:2018-09-03

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏猿人谷

字节大小

首先看一个例子: 1 #include <iostream> 2 using namespace std; 3 4 class A{}; 5 6...

208100
来自专栏大前端_Web

js浮点数加减乘除

版权声明:本文为吴孔云博客原创文章,转载请注明出处并带上链接,谢谢。 https://blog.csdn.net/wkyseo/articl...

21630
来自专栏C语言及其他语言

【编程经验】变量的存储类型

在 C 语言中,变量是对程序中数据所占内存空间的一种抽象定义,定义变量时,用户定义变量的名、 变量的类型,这些都是变量的操作属性。不仅可以通过变量...

11410
来自专栏海说

Dubbo源码学习--环境搭建及基础准备(ServiceLoader、ExtensionLoader)

环境搭建 Github上下载Dubbo最新发布版本,楼主下载版本为2.5.7。 cd到源码解压目录,maven编译,命令为: mvn clean install...

50700
来自专栏老司机的技术博客

人人都能学会的python编程教程10:调用函数

要调用一个函数,需要知道函数的名称和参数,比如求绝对值的函数abs,只有一个参数。

973130
来自专栏淡定的博客

python入门基础语法总结

11330
来自专栏小白鼠

SPIJava SPIDubbo SPI案例原理

SPI 全称为(Service Provider Interface),是JDK内置的一种服务提供发现机制。一个服务(Service)通常指的是已知的接口或者抽...

21820
来自专栏nnngu

018 final 关键字的用途

final关键字的含义 final在Java中是一个保留的关键字,可以声明成员变量、方法、类以及本地变量。一旦你将引用声明作final,你将不能改变这个引用了,...

37560
来自专栏Hongten

python开发_python中str.format()

21120
来自专栏和蔼的张星的图像处理专栏

684. 缺少的字符串分解到vector中借助find函数

先把两个字符串都分解到vector中,以空格为标志,然后在借助find函数来找出两个vector中不同的单词。

15130

扫码关注云+社区

领取腾讯云代金券