![](/img/trans.png)
[英]Scala semantics of equals/hashCode for case classes with traits
[英]What is the standard idiom for implementing equals and hashCode in Scala?
有一個免費的第 1 版 PinS 也討論了這個主題。 但是,我認為最好的來源是 Odersky 討論 Java 中的平等的這篇文章。 PinS中的討論是,iirc,這篇文章的縮寫版本。
在做了相當多的研究之后,我找不到一個答案,其中包含 Scala 類(不是 case 類,因為它是自動編譯器生成的,不應被覆蓋)的equals
和hashCode
模式的基本和正確實現。 我確實找到了一個2.10(陳舊)的宏,它勇敢地嘗試解決這個問題。
我最終結合了“Effective Java, 2nd Edition”(Joshua Bloch 着)和這篇文章“How to Write an Equality Method in Java”(Martin Odersky、Lex Spoon 和 Bill Venners 着)中推薦的模式,並創建了我現在用來為我的 Scala 類實現equals
和hashCode
的標准默認模式。
equals
模式的主要目標是最大限度地減少為獲得有效且明確的true
或false
所需執行的實際比較次數。
此外,當覆蓋equals
方法時,應始終覆蓋並重新實現hashCode
方法(再次參見“Effective Java, 2nd Edition”(由 Joshua Bloch 撰寫) )。 因此,我在下面的代碼中包含了hashCode
方法“模式”,其中還包含了有關在實際實現中使用##
而不是hashCode
重要建議。
值得一提的是, super.equals
和super.hashCode
中的每一個只有在祖先已經覆蓋它super.hashCode
必須調用它。 如果不是,則必須不調用super.*
作為java.lang.Object
的默認實現( equals
比較相同的類實例, hashCode
很可能將對象的內存地址轉換為整數) ,這兩者將為現在覆蓋的方法破壞指定的equals
和hashCode
協定。
class Person(val name: String, val age: Int) extends Equals {
override def canEqual(that: Any): Boolean =
that.isInstanceOf[Person]
//Intentionally avoiding the call to super.equals because no ancestor has overridden equals (see note 7 below)
override def equals(that: Any): Boolean =
that match {
case person: Person =>
( (this eq person) //optional, but highly recommended sans very specific knowledge about this exact class implementation
|| ( person.canEqual(this) //optional only if this class is marked final
&& (hashCode == person.hashCode) //optional, exceptionally execution efficient if hashCode is cached, at an obvious space inefficiency tradeoff
&& ( (name == person.name)
&& (age == person.age)
)
)
)
case _ =>
false
}
//Intentionally avoiding the call to super.hashCode because no ancestor has overridden hashCode (see note 7 below)
override def hashCode(): Int =
31 * (
name.##
) + age.##
}
該代碼有許多非常重要的細微差別:
scala.Equals
- 確保包含canEqual
方法形式化的equals
慣用模式正在完全實施。 雖然擴展它在技術上是可選的,但仍然強烈推薦它。(this eq person)
為true
可確保不再進行(昂貴的)比較,因為它實際上是同一個實例。 此測試需要在模式匹配內進行,因為eq
方法可用於AnyRef
,而不是Any
( that
的類型)。 並且因為AnyRef
是Person
的祖先,該技術通過對后代Person
進行類型驗證來同時進行兩個類型驗證,這意味着對其所有祖先(包括eq
檢查所需的AnyRef
進行自動類型驗證。 雖然此測試在技術上是可選的,但仍然強烈推薦。that
的canEqual
-這是很容易得到這種向后這是錯誤的。 以this
作為參數在that
實例上執行canEqual
的檢查至關重要。 雖然它對於模式匹配來說似乎是多余的(假設我們得到了這行代碼, that
必須是一個Person
實例),我們仍然必須進行方法調用,因為我們不能假設that
是Person
的相等兼容的后代(所有Person
后代將成功模式匹配為Person
)。 如果類被標記為final
,則此測試是可選的,可以安全地刪除。 否則,它是必需的。hashCode
短路 - 雖然不充分也不是必需的,但如果此hashCode
測試為false
,則無需執行所有值級別檢查(第 5 項)。 如果此測試為true
,則實際上需要逐個字段檢查。 此測試是可選的,如果未緩存 hashCode 值並且每個字段的相等性檢查的總成本足夠低,則可以排除此測試。hashCode
測試並成功,仍必須檢查所有字段級別的值。 這是因為,盡管這極不可能, 但兩個不同的實例仍然有可能生成完全相同的hashCode
值,並且在字段級別仍然實際上並不等效。 還必須調用父級的equals
以確保也測試祖先中定義的任何其他字段。case _ =>
- 這實際上是實現了兩種不同的效果。 首先,Scala 模式匹配保證null
在此處正確路由,因此null
不必出現在我們純 Scala 代碼中的任何地方。 其次,模式匹配保證無論that
是,這不是一個實例Person
或其后代之一。super.equals
和super.hashCode
有點棘手 - 如果祖先已經覆蓋了這兩個(不應該是任何一個),則必須將super.*
合並到您自己的覆蓋實現中。 如果祖先沒有覆蓋兩者,那么您覆蓋的實現必須避免調用super.*
。 上面的Person
代碼示例顯示了沒有覆蓋兩者的祖先的情況。 因此,調用每個super.*
方法調用將錯誤地一直到默認的java.lang.Object.*
實現,這將使equals
和hashCode
的假定組合合同無效。 這是基於super.equals
的代碼,僅當至少有一個祖先已經明確覆蓋equals
super.equals
使用。
override def equals(that: Any): Boolean =
...
case person: Person =>
( ...
//WARNING: including the next line ASSUMES at least one ancestor has already overridden equals; i.e. that this does not end up invoking java.lang.Object.equals
&& ( super.equals(person) //incorporate checking ancestor(s)' fields
&& (name == person.name)
&& (age == person.age)
)
...
)
...
這是基於super.hashCode
的代碼,僅當至少有一個祖先已經明確覆蓋了hashCode
super.hashCode
使用。
override def hashCode(): Int =
31 * (
31 * (
//WARNING: including the next line ASSUMES at least one ancestor has already overridden hashCode; i.e. that this does not end up invoking java.lang.Object.hashCode
super.hashCode //incorporate adding ancestor(s)' hashCode (and thereby, their fields)
) + name.##
) + age.##
最后一個注意事項:在我為此進行的研究中,我無法相信這種模式存在多少錯誤的實現。 很明顯,這仍然是一個難以准確把握細節的領域:
hashCode
覆蓋和實現時,在類字段上錯誤地使用.hashCode
而不是.##
。 參見 Tree3.scala是的,覆蓋equals
和hashCode
在 Java 和 Scala 中都是一項艱巨的任務。 我建議根本不要使用equals
,而是使用類型類( Eq
/ Eql
等)。 它更類型安全(比較不相關類型時編譯器錯誤),更容易實現(沒有覆蓋和類檢查)並且更靈活(您可以編寫與數據類分開的類型類實例)。 Dotty 使用了“多元等式”的概念,它提供了在捕獲一些明顯不正確的equals
用法和嚴格相等性檢查之間的選擇。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.