我有一个简单的数据类,它存储一个位置的x和y坐标。我的用例是创建和更新该类的单个对象,并且我需要维护一组唯一的坐标。
我在下面的代码中简化了我的用例,其中将pos
对象直接添加到集合中与传递对象的副本会导致不同的行为(请参阅代码中的注释)。
我最初的预感是,这可能是因为Java/Kotlin通过引用传递对象,Set.add
在引用上进行比较。然而,这似乎不是真的,如果我将pos.x
或pos.y
设置为任何其他值,那么set.contains
方法返回false。
问题:如果比较是通过引用,那么为什么在设置为以下代码中给出的值以外的值时失败?如果比较是通过哈希码那么为什么setByCopy
不返回真在原来的情况下?
data class Pos(var x: Int = 0, var y: Int = 0)
fun main() {
val pos = Pos(0, 0)
val set = mutableSetOf<Pos>()
val setByCopy = mutableSetOf<Pos>()
pos.x = -9
pos.y = -6
set.add(pos)
setByCopy.add(pos.copy())
println(pos.hashCode())
pos.x = -8
pos.y = -37
// setting pos.y to any other value (e.g -35) will cause set.contains(pos) to return false.
println(set.contains(pos)) // true, but expected false.
println(setByCopy.contains(pos)) // false
}
通常,修改已经存在于集合中的元素会产生未定义的行为。这在Kotlin中没有明确的文档,但是从Java继承过来,在Java中有文档:
如果将可变对象用作集合元素,必须非常小心。
如果对象是集合中的一个元素,而对象的值以影响相等比较的方式更改,则不指定集合的行为。
这意味着任何事情都可能发生:它可以随机工作或不工作。
您正在创建两个对象,pos
,然后是一个单独的,我们将其称为pos2
。当您在数据类的实例上调用copy()
时,您将获得一个完全独立的实例,其属性初始化为相同的数据。
然后将每个实例添加到单独的Set
。即使set
包含pos
,setByCopy
包含pos2
,如果调用setByCopy.contains(pos)
,那么它将返回true,因为相等性对集合和数据类是如何工作的:
boolean contains(Object o)
如果此集合包含指定的元素则返回true。更正式地说,当且仅当此集合包含满足以下条件的元素e
(o==null ? e==null : o.equals(e))
返回true.
这个o.equals(e)
位是很重要的——一个数据类会根据它的数据自动生成一个equals()
实现,即构造函数中的属性。所以Pos(0, 0) == Pos(0, 0)
是true尽管它们是不同的实例,因为它们包含相同的数据。
这就是setByCopy.contains(pos)
为真的原因——不是因为它包含对象,而是因为它包含一个与相等的对象。
当你用不同的数字更新pos
时,现在pos
和pos2
的数据属性有不同的值-它们不再等于,所以setByCopy.contains(pos)
返回false。
set.contains(pos)
仍然求值为true,因为该集合包含pos
对象。当你更新那个对象时,集合中的引用指向同一个对象,所以它当然等于它自己!如果你想创建一个独立的实例,当你更新pos
时不会改变,那么这就是copy()
对于