我有一个简单的数据类,它存储一个位置的x和y坐标。我的用例是创建和更新这个类的单个对象,我需要维护一组唯一的坐标。
我已经在下面的代码中简化了我的用例,其中直接将pos
对象添加到集合与传递对象的副本会导致不同的行为(请参见代码中的注解)。
我最初的直觉是,这可能是因为Java/Kotlin通过引用传递对象,而Set.add
在引用上进行比较。然而,这似乎不是真的,如果我将pos.x
或pos.y
设置为任何其他值,那么set.contains
方法返回false。
问:如果比较是通过引用,那么为什么在设置为下面代码中给出的值以外的值时会失败?如果比较是通过哈希代码,那么为什么setByCopy
在原始情况下不返回true?
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
}
2条答案
按热度按时间djp7away1#
通常,修改集合中的元素会产生未定义的行为。Kotlin中没有明确说明这一点,但它是从Java继承而来的,在Java中有说明:
如果使用可变对象作为集合元素,则必须非常小心。如果对象的值以影响等于比较的方式更改,而该对象是集合中的元素,则不指定集合的行为。
这意味着任何事情都可能发生:它可以随机工作或不工作。
uqzxnwby2#
你创建了两个对象,
pos
和一个单独的pos2
,当你在一个数据类的示例上调用copy()
时,你会得到一个完全独立的示例,它的属性被初始化为相同的数据。然后将每个示例添加到一个单独的
Set
中。即使set
包含pos
,setByCopy
包含pos2
,如果调用setByCopy.contains(pos)
,那么它将返回 true,因为等式是如何作用于集合和数据类的:boolean contains(Object o)
如果此集合包含指定的元素,则返回true。更正式地说,当且仅当此集合包含满足**
(o==null ? e==null : o.equals(e))
**的元素e时,才返回true。o.equals(e)
位是很重要的--数据类会根据它的数据自动生成equals()
实现,即构造函数中的属性。所以Pos(0, 0) == Pos(0, 0)
是 true,即使它们是不同的示例,因为它们包含相同的 data。这就是为什么
setByCopy.contains(pos)
为真--不是因为它包含 * 那个对象 *,而是因为它包含一个等于它的对象 *。当您使用不同的数字更新
pos
时,pos
和pos2
的data属性值不同-它们不再 * 相等 *,因此setByCopy.contains(pos)
返回 false。set.contains(pos)
仍然评估为 true,因为 * 该集合包含pos
对象 *。当您更新该对象时,集合中的引用指向同一个对象,因此它当然等于它自己!如果您希望创建一个独特的、独立的示例,并且在您更新pos
时 * 不 * 更改,那么copy()
就是这样做的