左手的世界
Rise's Blog
(译)Kotlin中的等号,"==","==="和"equals"
我们该如何正确使用Kotlin里面这些相等性判断呢?

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)。

	val int1 = 10
	val int2 = 10

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

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

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

	val first = Integer(10)
	val second = Integer(10)

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

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

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

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类:

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”:

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/ 十分感谢作者写的文章,以及授权本人翻译,以分享给中国开发者。 原作者保留所有权利。


上次修改於 2019-05-18