为什么String类是不可变的,即使它有一个名为“hash”的非最终字段

我正在阅读约书亚布洛赫的有效Java第15项。 在第15项中谈到“最小化可变性”时,他提到了使对象不可变的五条规则。 其中之一就是让所有领域都是最终的。 这是规则:

使所有字段成为最终字段 :这清楚地以系统强制执行的方式表达您的意图。 此外,如果对新创建的实例的引用在没有同步的情况下从一个线程传递到另一个线程,则必须确保正确的行为,如内存模型中所述[JLS,17.5; Goetz06 16]。

我知道String类是一个不可变类的例子。 通过源代码我看到它实际上有一个非最终的哈希实例。

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

String如何成为不可变的呢?

该评论解释了为什么这不是最终的:

//缓存字符串的哈希码

这是一个缓存。 如果不调用hashCode ,则不会设置它的值。 它可以在创建字符串期间设置,但这意味着更长的创建时间,对于您可能不需要的function(哈希代码)。 另一方面,每次询问时计算散列都是浪费,给字符串是不可变的 ,并且散列码永远不会改变。

有一个非final字段的事实确实与你引用的定义有些矛盾,但这里它不是对象接口的一部分。 它只是一个内部实现细节,它对字符串的可变性没有影响(作为字符容器)。

编辑 – 由于受欢迎的需求,完成我的答案:虽然hash不是公共接口的直接部分,但它可能会影响该接口的行为,因为hashCode返回其值。 现在,由于hashCode未同步,如果多个线程同时使用该方法,则可能会多次设置hash 。 但是,设置为hash值始终是稳定计算的结果,该计算仅依赖于最终字段( valueoffsetcount )。 因此,每次计算哈希都会产生完全相同的结果。 对于外部用户,这就像hash计算一次 – 就像每次都计算hash一样,因为hashCode的契约要求它始终为给定值返回相同的结果。 最重要的是,即使hash不是最终的,它的可变性永远不会被外部查看者看到,因此该类可以被认为是不可变的。

String是不可变的,因为就其用户而言,它永远不会被修改,并且对所有线程看起来总是相同。

hashCode()是使用racy single-check成语(EJ item 71)计算的,它是安全的,因为如果hashCode()被意外地多次计算,它不会伤害任何人。

使所有字段成为最终是使类不可变的最简单和最简单的方法,但并不是严格要求的。 只要所有方法返回相同的东西,无论哪个线程调用它,该类都是不可变的。

即使String是不可变的,它也可以通过reflection改变。 如果你做最后的哈希,如果发生这种情况,你可能会把事情弄得一团糟。 哈希字段也是不同的,因为它主要是作为缓存,一种加速hashCode()的计算的方法,应该被认为是一个计算字段,而不是常量。

在许多情况下,对于逻辑上不可变的类,对于相同的可观察状态具有若干不同的表示,并且类的实例能够在它们之间切换,这可能是有帮助的。 将从其哈希字段为零的字符串返回的哈希码值将与哈希字段保持早期哈希码调用的结果时返回的值相同。 因此,将哈希值从前者更改为后者不会更改对象的可观察状态,但会导致将来的操作运行得更快。

以这些方式编码事物的最大困难是

  1. 如果将对象从对某个特定不可变对象的引用更改为持有对具有相同语义内容的其他对象的引用,则此类更改不应影响保持引用的对象的可观察状态,但如果结果是假设相同的对象并不完全相同,可能会发生不好的事情,特别是如果假定持有引用的对象被认为可替代其他语义相同的对象。
  2. 即使没有任何对象“相同”的错误,仍然存在这样的危险,即与替换的线程看起来相同的对象可能看起来与其他线程不同。 这种情况不太可能发生,但如果确实发生,效果可能非常糟糕。

尽管如此,对不可变对象进行替换仍然有一些优点。 例如,如果程序将比较许多持有长字符串的对象,并且其中许多对象虽然是单独生成的,但它们彼此相同,使用WeakDictionary构建不同字符串实例池并替换它们可能很有用。发现与池中的一个字符串相同的任何字符串,其中包含对池副本的引用。 这样做会导致许多相同的字符串被映射到相同的字符串,从而极大地促进了它们之间可能进行的任何未来比较。 当然,正如所指出的那样,对象在逻辑上是不可变的非常重要,比较是正确完成的。 在这方面的任何问题都可以将优化变成混乱。

创建一个不可变的对象你需要使类最终及其所有成员都是final,这样一旦对象被创建,没有人可以修改它的状态。 您可以通过将成员设为非最终但私有而不在构造函数中修改它们来实现相同的function

编辑:

注意:当散列字符串时,Java还会在散列属性中缓存散列值,但前提是结果不等于零。