Kotlin:处理集合时的异常行为



我有一个简单的数据类,它存储一个位置的x和y坐标。我的用例是创建和更新该类的单个对象,并且我需要维护一组唯一的坐标。

我在下面的代码中简化了我的用例,其中将pos对象直接添加到集合中与传递对象的副本会导致不同的行为(请参阅代码中的注释)。

我最初的预感是,这可能是因为Java/Kotlin通过引用传递对象,Set.add在引用上进行比较。然而,这似乎不是真的,如果我将pos.xpos.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时,现在pospos2的数据属性有不同的值-它们不再等于,所以setByCopy.contains(pos)返回false

set.contains(pos)仍然求值为true,因为该集合包含pos对象。当你更新那个对象时,集合中的引用指向同一个对象,所以它当然等于它自己!如果你想创建一个独立的实例,当你更新pos不会改变,那么这就是copy()对于

的作用。

最新更新