为什么在引用不同的情况下,引用相等检查返回true

考虑这个代码:

fun main(args : Array<String>) { println("Async" == MetricCategory.Async.toString()) println("Async" === MetricCategory.Async.toString()) } 

它输出

 true true 

而我期待

 true false 

为什么在第二次检查时打印出来的原因是两个参考文献都不一样

引用相等不是变量名称是相同的,或者是以相同的方式访问的,也就是说,内存中的位置是相同的。 由于字符串是不可变的,编译器通常可以预先为它们预留内存,并且对同一个地方的所有引用都是相同的值。

不变性是很重要的,因为在读/写引用不同的情况下共享只读引用是安全的。 如果您不正确地在可变数据结构之间共享引用,则来自一组引用的修改将反映在另一组引用中,导致出现奇怪和不正确的行为。 但是,如果数据不能再更改,则可以通过将所有内容都指向相同的数据来自由地保存尽可能多的内存。

根据如何实现MetricCategory.Async.toString() ,操作的结果可能是任意的。 考虑下面的例子:

 class MetricCategory { object Async { override fun toString(): String { return "Async" } } } 

这个实现会导致truetrue打印出来。 如文档所述===运算符比较引用相等 :

当且仅当a和b指向相同的对象时才评估为真。

但为什么2个常量字符串表达式是同一个对象呢? 这是由JVM(和其他运行时)称为字符串interning的功能引起的:

在计算机科学中,字符串实习是一种仅存储每个不同字符串值的一个副本的方法,它必须是不可变的。 实习中的字符串使一些字符串处理任务的时间或空间效率更高,但需要更多的时间来创建或实施字符串。 不同的值存储在字符串实习生池中。

字符串interning不会在JVM中自动发生,但可以手动触发。

 class MetricCategory { object Async { override fun toString(): String { val result = "a".toUpperCase() + "SYNC".toLowerCase() return result.intern() } } } 

上面的例子将打印true ,再次,但只是因为我们已经调用String.intern

考虑下面的例子:

 println("Async" == "Async") // true, obviously println("Async" === "Async") // true, string interning for literals println("Async" == java.lang.String("Async").toString())// true, obviously println("Async" === java.lang.String("Async").toString()) // false, new instance on the right println("Async" === java.lang.String("Async").toString().intern()) // true, right changed to shared instance 

进一步阅读:

  • 什么是字符串实习?
  • 字符串对象和字符串之间的区别
  • 字符串interning真的有用吗?