引用相等性与结构相等性
Kotlin 中两种基本的相等类型及其使用方法
定义
在任何编程语言中,我们有两种方式来比较对象并检查它们是否相等:
- 引用相等性,
- 结构相等性。
引用相等性指的是两个对象指向内存中完全相同的对象,即它们使用相同的引用。
结构相等性则意味着两个对象具有相同的值。尽管如此,它们仍可能指向内存中的不同位置,即它们不一定持有相同的引用。
注意: 引用相等性蕴含结构相等性。
在 Kotlin 中,我们为这两种相等性提供了对应的运算符:
- 使用
===检查引用相等性 - 使用
==检查结构相等性
代码示例
1val starters1 = listOf("charmander", "squirtle", "bulbasaur") // 一个列表对象 2val starters2 = listOf("charmander", "squirtle", "bulbasaur") // 另一个内容相同但不同的列表 3 4println(starters1 === starters2) // false:变量指向不同的内存位置 5println(starters1 == starters2) // true:变量持有的值相等 6println(starters1 !== starters2) // true:第一个比较的否定 7println(starters1 != starters2) // false:第二个比较的否定
两个对象是否结构相等取决于它们的类型,更具体地说,取决于其 equals 方法的实现方式。
实际上,Kotlin 的 == 运算符用于结构相等时会调用对象的 equals 方法进行比较。Kotlin 默认的 a == b 实现是 a?.equals(b) ?: (b === null)。这意味着:
- 如果
a不为null,则它必须与b相等(根据其equals方法的实现) - 如果
a为null,则b也必须为null
注意: Kotlin 会自动将
a == null转换为a === null。因此你无需手动做这种微优化。
equals 的特性
任何 equals 方法的实现都必须满足以下性质:
- 自反性: 对于任意非空值
x,x.equals(x)应返回 true。 - 对称性: 对于任意非空值
x和y,当且仅当y.equals(x)返回 true 时,x.equals(y)才返回 true。 - 传递性: 对于任意非空值
x、y和z,如果x.equals(y)返回 true 且y.equals(z)返回 true,则x.equals(z)也应返回 true。 - 一致性: 对于任意非空值
x和y,在未修改equals比较所依赖的信息的前提下,多次调用x.equals(y)会一致地返回 true 或一致地返回 false。 - 永不等于 null: 对于任意非空值
x,x.equals(null)应返回 false。
练习
一种奇怪的现象
让我们探索 JVM 的一个特殊行为:
- 编写一个返回类型为
Int?的函数,并让它返回某个固定的整数(忽略编译器提示“你总是返回非空值”的警告) - 创建两个类型为
Int?的变量,并调用你的函数为其赋值 - 检查它们是否 a) 结构相等,以及 b) 引用相等
- 探究当函数返回值在
-128到127之间时会发生什么,以及当返回值超出此范围时又会发生什么
注意: 我们使用可空的
Int?类型,是为了让编译器在运行时将其作为 JVM 上的Integer对象处理,而不是原始类型int。
解答
1fun couldBeNull(): Int? = 128 // 尝试改为 127 2 3fun main() { 4 val nullable1: Int? = couldBeNull() 5 val nullable2: Int? = couldBeNull() 6 7 println("nullable1 == nullable2: ${nullable1 == nullable2}") 8 println("nullable1 === nullable2: ${nullable1 === nullable2}") 9}
你可能会认为这两个整数对象永远不会是引用相等的,因为它们不是基本类型。
然而,对于 -128 到 127 之间的较小数值,它们实际上共享了相同的引用。这是因为 Java(以及 Kotlin)内部使用 Integer.valueOf() 将你的整数字面量(例如 127)转换为 Integer 对象——而该方法会对 -128 到 127 之间的整数进行缓存。
提示: 你应该从中吸取什么教训?为了避免这类情况,应始终使用
==来比较Int?和其他“可空基本类型”。
总结
现在你已经清楚地了解了 Kotlin 中 == 和 === 的工作原理及其区别:
==检查结构相等性,即两个对象是否根据其equals方法的实现被认为是“相等”的===检查引用相等性,即两个变量是否指向内存中的同一位置- 应谨慎使用
===
在下一课中,你将学习浮点数相等性的具体细节。
Quiz
以下哪些是结构上相等的?
你可以选择多个选项。