2011-09-25 71 views
10

我發現了可變套一個奇怪的行爲,我無法理解:斯卡拉:包含可變和不可變套

我有我想要添加到一組對象。該類的equals方法被覆蓋。當我向這個集合添加兩個不同的對象時,它爲equals方法產生相同的輸出,我在contains方法的mutable集合和immutable集合之間獲得了不同的行爲。

這裏是代碼片段:

class Test(text:String){ 
    override def equals(obj:Any) = obj match { 
    case t: Test => if (t.text == this.text) true else false 
    case _ => false 
    } 
    override def toString = text 
} 

val mutableSet:scala.collection.mutable.Set[Test] = scala.collection.mutable.Set.empty 
mutableSet += new Test("test") 
println(mutableSet) 
println(mutableSet.contains(new Test("test"))) 

val immutableSet:scala.collection.immutable.Set[Test] = scala.collection.immutable.Set.empty 
immutableSet += new Test("test") 
println(immutableSet) 
println(immutableSet.contains(new Test("test"))) 

這將產生作爲輸出:

Set(test) 
false 
Set(test) 
true 

在我看來兩個通話包含應產生相同的輸出(真)。

任何人都可以幫助我理解這裏的區別或者這是一個在scala不可變set實現中的錯誤嗎?順便說一句,我用斯卡拉2.8.1.final

謝謝。

回答

23

規則1實現equals()時:同時實現hashCode()。請參閱Overriding equals and hashCode in Java

在第一個示例中,您將創建一個可調集合,它將調用hashCode來設置哈希表。

第二,你使用一個條目的不可變集合,所以Scala實際上使用集合的優化版本,稱爲Set1。 Set1.contains()只是直接使用equals()比較一個條目和傳入的元素。這看起來像:

/** An optimized representation for immutable sets of size 1 */ 
@SerialVersionUID(1233385750652442003L) 
class Set1[A] private[collection] (elem1: A) extends Set[A] with Serializable { 
    override def size: Int = 1 
    def contains(elem: A): Boolean = 
    elem == elem1 
    def + (elem: A): Set[A] = 
    if (contains(elem)) this 
    else new Set2(elem1, elem) 
    def - (elem: A): Set[A] = 
    if (elem == elem1) Set.empty 
    else this 
    def iterator: Iterator[A] = 
    Iterator(elem1) 
    override def foreach[U](f: A => U): Unit = { 
    f(elem1) 
    } 
} 

沒有調用hashCode。還有一個Set2,Set3和Set4。如果我們改變

所以你的代碼是:

class Test(val text:String){ 
    override def equals(obj:Any) = { 
    println("equals=" + obj) 
    obj match { 
    case t: Test => if (t.text == this.text) true else false 
    case _ => false 
    }} 

    override def hashCode(): Int = { 
    println("hashCode=" + super.hashCode()) 
    super.hashCode() 
    } 
    override def toString = text 
} 

println("mutable") 
val mutableSet:scala.collection.mutable.Set[Test] = scala.collection.mutable.Set.empty 
mutableSet += new Test("test") 
println("mutableSet=" + mutableSet + " contains=" + mutableSet.contains(new Test("test"))) 

println("immutable") 
var immutableSet:scala.collection.immutable.Set[Test] = scala.collection.immutable.Set.empty 
immutableSet += new Test("test") 
println("immutableSet=" + immutableSet + " contains=" + immutableSet.contains(new Test("test"))) 

添加一個hashCode,並在平等的一個println,輸出是:

mutable 
hashCode=30936685 
hashCode=26956691 
mutableSet=Set(test) contains=false 
immutable 
equals=test 
immutableSet=Set(test) contains=true 

這解釋了爲什麼mutable.contains( )工作不正常。它在錯誤的哈希表項​​中查找對象,equals()甚至沒有被調用。毫不奇怪,它沒有找到它。

可以使用text.hashCode實現的hashCode:

override def hashCode: Int = text.hashCode 
+0

正如Matthew所說:-) –

+0

謝謝,我還沒有意識到,有大小爲1的不變集的特殊情況。 – Stefan

7

你需要重寫hashCode爲好。當您覆蓋equals時,必須覆蓋hashCode

注也有未編譯的幾件事情,所以我編輯多一點:

class Test(val text:String){ // added val 
    override def equals(obj:Any) = obj match { 
    case t: Test => if (t.text == this.text) true else false 
    case _ => false 
    } 
    override def toString = text 
    override def hashCode = text.hashCode 
} 

val mutableSet:scala.collection.mutable.Set[Test] = scala.collection.mutable.Set.empty 
mutableSet += new Test("test") 
println(mutableSet) 
println(mutableSet.contains(new Test("test"))) 

val immutableSet:scala.collection.immutable.Set[Test] = scala.collection.immutable.Set.empty 
val immutableSet2 = immutableSet + new Test("test") // reassignment to val 
println(immutableSet2) 
println(immutableSet2.contains(new Test("test"))) 

我建議做對象平等多了很多的見解閱讀http://www.artima.com/pins1ed/object-equality.html。這是開眼界。

+0

對不起,語法錯誤。下次我會看兩次。 – Stefan

+0

您上面提供的代碼正常工作。馬修說,「奇怪」行爲的原因是。 – Stefan