(译)Kotlin中的等号,"==","==="和"equals"

Translated from: Equality in Kotlin
Author: Suneet Agrawal
Translated by : AmazingRise (Authorized by original author)

在编程中,我们经常需要比较两个变量的值是否相等,或者两个对象的引用是否一致。
Kotlin语言里,“等号”有这么几种:=====.equals
那么问题来了,我们该如何正确使用这些相等性判断呢?

我们来一起看一看Kotlin的几种相等性判断:

结构性相等 (“==”)

==操作符用于比较两个变量的值。
请不要与Java中的==相混淆。
与Java中的==操作符不同的是:==操作符在Kotlin中只比较值。
而Java或其它语言中,==通常用来比较两个对象的引用。
Kotlin中,==的否定形式是!=,当两个变量值不同时返回真。

引用性相等(“===”)

===操作符用于比较两个变量的引用是否一致。只有当两个变量指向同一个实例时,===的值才为真。
===的否定形式是!==,当两个对象的引用不同的时候返回真。

不过对于原始类型(Primitive type)来说(例如 Int),===等价于==

.equals 方法

equals(other: Any?)方法是在Any类里实现的。
并且所有类中.equals都可以被重写(毕竟所有的类都继承于Any嘛,就像Java里的Object一样)。

.equals方法将会比较两个变量的值。
==不同体现在比较FloatDouble的时候,.equals违背了IEEE 754 浮点数运算标准。

与IEEE 754浮点数运算标准相违背,意味着什么?

这意味着:

  • NaN 等于它本身
  • NaN 比任何其他元素要大,包括正无穷 POSITIVE_INFINITY
  • 编译器会认为-0.0 要比 0.0 小。

一头雾水?

举个栗子

我来举几个例子解释一下。

首先,我们用这几个方法比较两个原始类型的变量(Int)。

1
2
3
4
5
6
val int1 = 10
val int2 = 10

println(int1 == int2) // true
println(int1.equals(int2)) // true
println(int1 === int2) // true

这三个println最后都会输出true,因为对于原始类型,这三种判断都仅仅检查变量的值。
所以在这种情况下,===输出的结果也是一样的。

现在让我们把原始类型换成包装类,看看三种方法表现出的行为。

1
2
3
4
5
6
val first = Integer(10)
val second = Integer(10)

println(first == second) //true
println(first.equals(second)) //true
println(first === second) //false

在上面的例子中,==.equals输出了true,因为他们仅仅比较了两个变量的值。
===比较了对象的引用,而这两个对象的引用是不同的,所以最后输出了false

现在我们来看另一种情况。我们自己定义一个类,然后再用这三个方法:

1
2
3
4
5
6
7
8
9
10
11
12
class Employee (val name: String)

val emp1 = Employee(“Suneet”)
val emp2 = Employee(“Suneet”)

println(emp1 == emp2) //false
println(emp1.equals(emp2)) //false
println(emp1 === emp2) //false

println(emp1.name == emp2.name) //true
println(emp1.name.equals(emp2.name)) //true
println(emp1.name === emp2.name) //true

结果显而易见。因为Employee类既不是原始数据类型,也不是包装类,这种情况下它们都会比较两个变量的引用,所以它们都返回了false
但是对于字符串的比较,这三个方法都只检查了字符串的内容,所以它们都返回了true

等等,你说==.equals只比较两个对象的内容,在我们的这个例子里它们都是一样的呀
(译者注:指第一段,三个比较全都返回了false)
这没毛病。但内容比较仅对于data类起作用。对于普通的类,即使两个对象内容完全一致,编译器也会认为两个对象是不同的。而对于data类,编译器将比较对象的内容,并且在内容相同的情况下返回true

让我们把上面的普通类改为data类:

1
2
3
4
5
6
7
8
9
10
11
12
data class Employee (val name: String)

val emp1 = Employee("Suneet")
val emp2 = Employee("Suneet")

println(emp1 == emp2) //true
println(emp1.equals(emp2)) //true
println(emp1 === emp2) //false

println(emp1.name == emp2.name) //true
println(emp1.name.equals(emp2.name)) //true
println(emp1.name === emp2.name) //true

这回就没问题了。最后我们看一下浮点数的比较,我们来看看“正数0”和“负数0”:

1
2
3
4
5
6
val negZero = -0.0f
val posZero = 0.0f

println(negZero == posZero) //true
println(negZero.equals(posZero)) //false
println(negZero === posZero) //true

对于FloatDouble类的比较,.equals方法违背了IEEE 754 浮点数运算标准,当-0.00.0比较的时候,它会返回false
=====都会返回true

最后谨记

  • 因为Kotlin里没有像String("")这样的构造器,当比较字符串的时候,只要内容相等,都会返回true
  • 对于null引用的判断,我们不必做任何优化。a == null 会被自动转换为 a === null,因为null本身也是一个引用,它将会引发引用检查(而不是值检查)。

就酱。点击这里看看我写的其他有趣文章。
或者点击这里,瞧一瞧我写的App和游戏。
另外,欢迎在你的App里面使用我的Android开源组件
如果你需要帮助的话,可以给我发邮件: agrawalsuneet AT gmail.com。

引用: Kotlin docs


翻译完结。

作者:Suneet Agrawal.
网站:https://agrawalsuneet.github.io/agrawalsuneet/
十分感谢作者写的文章,以及授权本人翻译,以分享给中国开发者。
原作者保留所有权利。