scala case class 带有循环引用的哈希码
scala case class hashcode with circular references
正如 Scala 文档所说,"Case classes are compared by structure and not by reference"。
我试图创建一个带有循环的链表,然后将这个循环列表中的一个节点添加到一个可变集。当使用常规 class 时,它成功了,但是对于 class 我遇到了堆栈溢出。
我很好奇为什么 Scala 在检查结构之前不先检查引用。我的想法是,如果引用相同,则结构保证相同,所以这可以是一个捷径,也可以允许循环引用。
代码:
object CaseClassHashcodeExample {
def main(args: Array[String]): Unit = {
val head = new ListNode(0)
head.appendToTail(1)
head.appendToTail(2)
head.appendToTail(3)
head.next.next.next = head.next
val set = collection.mutable.Set[ListNode]()
set.add(head)
assert(set(head))
}
}
case class ListNode(var data: Int, var next: ListNode = null) {
def appendToTail(d: Int): Unit = {
val end = new ListNode(d)
var cur = this
while (cur.next != null)
cur = cur.next
cur.next = end
}
}
I'm curious why Scala doesn't first check for reference before checking for structure
好吧,究竟你想让它检查什么?当前生成的代码看起来像
def hashCode() = 31 * data.hashCode() + next.hashCode()
您可以尝试的一件事是
def hashCode() = 31 * data.hashCode() + (if (next eq this) 0 else next.hashCode())
但它实际上并没有帮助:在您的情况下,它不是 next
,它与 this
相同,而是 next.next
。
next.hashCode
不知道它是从 this.hashCode
调用的,因此无法比较它自己的 next
和 this
。
您可以创建一个考虑一组 "seen" 个对象的辅助方法:
def hashCode() = hashCode(Nil)
def hashCode(seen: List[ListNode]) = if (seen.exists(_ eq this)) 0 else 31 * data.hashCode() + next.hashCode(this :: seen)
但这都大大减慢了常见情况,而且很难真正做到正确。
编辑:对于 equals
,引用相等性 是 首先检查以防万一 类。
class NeverEq {
override def equals(other: Any) = false
}
case class Test(x: NeverEq)
val x = Test(new NeverEq())
println(x == x)
打印 true
如果只比较结构,将是 false
。
但它实际上对循环引用没有帮助。假设您有一个没有 data
的 ListNode
类型来简化,它实现了这样的相等性:
def equals(other: Any) = (this eq other) || (other match {
case other: ListNode => this.next == other.next
})
并想检查 node1 == node2
是否 node1.next
是 node2
,反之亦然:
node1 <--> node2
node1 == node2
减少为 (node1 eq node2) || (node1.next == node2.next)
.
node1 eq node2
是假的,所以减少到 node1.next == node2.next
,即 node2 == node1
.
node2 eq node1
为假,因此减少为 node2.next == node1.next
,即 node1 == node2
...
正如 Scala 文档所说,"Case classes are compared by structure and not by reference"。
我试图创建一个带有循环的链表,然后将这个循环列表中的一个节点添加到一个可变集。当使用常规 class 时,它成功了,但是对于 class 我遇到了堆栈溢出。
我很好奇为什么 Scala 在检查结构之前不先检查引用。我的想法是,如果引用相同,则结构保证相同,所以这可以是一个捷径,也可以允许循环引用。
代码:
object CaseClassHashcodeExample {
def main(args: Array[String]): Unit = {
val head = new ListNode(0)
head.appendToTail(1)
head.appendToTail(2)
head.appendToTail(3)
head.next.next.next = head.next
val set = collection.mutable.Set[ListNode]()
set.add(head)
assert(set(head))
}
}
case class ListNode(var data: Int, var next: ListNode = null) {
def appendToTail(d: Int): Unit = {
val end = new ListNode(d)
var cur = this
while (cur.next != null)
cur = cur.next
cur.next = end
}
}
I'm curious why Scala doesn't first check for reference before checking for structure
好吧,究竟你想让它检查什么?当前生成的代码看起来像
def hashCode() = 31 * data.hashCode() + next.hashCode()
您可以尝试的一件事是
def hashCode() = 31 * data.hashCode() + (if (next eq this) 0 else next.hashCode())
但它实际上并没有帮助:在您的情况下,它不是 next
,它与 this
相同,而是 next.next
。
next.hashCode
不知道它是从 this.hashCode
调用的,因此无法比较它自己的 next
和 this
。
您可以创建一个考虑一组 "seen" 个对象的辅助方法:
def hashCode() = hashCode(Nil)
def hashCode(seen: List[ListNode]) = if (seen.exists(_ eq this)) 0 else 31 * data.hashCode() + next.hashCode(this :: seen)
但这都大大减慢了常见情况,而且很难真正做到正确。
编辑:对于 equals
,引用相等性 是 首先检查以防万一 类。
class NeverEq {
override def equals(other: Any) = false
}
case class Test(x: NeverEq)
val x = Test(new NeverEq())
println(x == x)
打印 true
如果只比较结构,将是 false
。
但它实际上对循环引用没有帮助。假设您有一个没有 data
的 ListNode
类型来简化,它实现了这样的相等性:
def equals(other: Any) = (this eq other) || (other match {
case other: ListNode => this.next == other.next
})
并想检查 node1 == node2
是否 node1.next
是 node2
,反之亦然:
node1 <--> node2
node1 == node2
减少为(node1 eq node2) || (node1.next == node2.next)
.node1 eq node2
是假的,所以减少到node1.next == node2.next
,即node2 == node1
.node2 eq node1
为假,因此减少为node2.next == node1.next
,即node1 == node2
...