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

Inq*_*ive 21 java string immutability

我正在阅读Joshua Bloch撰写的有效Java第15项.在第15项中谈到"最小化可变性"时,他提到了使对象不可变的五条规则.其中之一就是让所有领域都是最终的.这是规则:

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

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

//Cache the hash code for the string
private int hash; // Default to 0
Run Code Online (Sandbox Code Playgroud)

String如何成为不可变的呢?

era*_*ran 26

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

//缓存字符串的哈希码

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

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

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

  • 主要是最后一句话的问题.`hashCode`方法*是对象接口的一部分,它是一个公共方法.如果结果在不同的调用上发生变化,则该对象将是可变的.无论它是否影响包含的字符.按照"良性数据竞赛"的描述来描述它,以及如何通过呼叫者看不到对象如@Louis Wasserman所做的那样,将看到downvote变为up :) (3认同)
  • @Grundlefleck,我明白你的观点,但即使`hashCode`是公共接口的一部分,`hash`也不是.鉴于它是私有的,只有`String`类可以改变它.现在,由于存在于`String`中的字符不会改变,因此`String`不会多次改变`hash`,因为它会破坏`hashCode`契约(这比破坏可变性_definition_更糟糕) ,IMO).因此,虽然`hash`不是严格的`final`,但它的使用语义足以说`hash`的可变性不会破坏`String`的可变性. (2认同)

Lou*_*man 8

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

hashCode()使用生动的单一检查成语(EJ第71项)hashCode()来计算它,并且它是安全的,因为如果不小心计算了多次,它不会伤害任何人.

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