2011-09-25 10 views
10

odkryłam dziwne zachowanie dla zestawów modyfikowalnych których nie mogę zrozumieć:Scala: Zawiera modyfikowalnych i niezmiennych w zestawach

Mam obiektu, który chcę dodać do zestawu. Metoda równań dla klasy jest nadpisywana. Kiedy dodaję dwa różne obiekty do zestawu, który produkuje ten sam wynik dla metody równości, otrzymuję inne zachowanie między zmiennymi i niezmiennymi zestawami dla metody zawiera.

Oto fragment kodu:

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"))) 

To daje na wyjściu:

Set(test) 
false 
Set(test) 
true 

Moim zdaniem oba wywołania zawiera powinien produkować taką samą moc (true).

Czy ktoś mógłby mi pomóc zrozumieć różnicę tutaj lub czy jest to błąd w niezmiennej implementacji zestawu scala? Przy okazji używam scala 2.8.1.final

Dzięki.

Odpowiedz

23

Reguła 1 przy implementacji equals(): Implementuje funkcję hashCode() w tym samym czasie. Patrz: Overriding equals and hashCode in Java

W pierwszym przykładzie tworzony jest zmienny zestaw wywołujący funkcję hashCode w celu ustawienia tabeli mieszania.

Po drugie, używasz niezmiennego zestawu z jednym wpisem, więc Scala faktycznie używa zoptymalizowanej wersji zestawu o nazwie Set1. Set1.contains() porównuje tylko jeden wpis z przekazanym elementem za pomocą equals() bezpośrednio. To wygląda następująco:

/** 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) 
    } 
} 

Nie jest wywołany kod skrótu. Istnieje również Set2, Set3 i Set4.

Jeśli więc zmiany kodu będzie:

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"))) 

dodanie hashCode i println w równych, a wyjście jest:

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

co wyjaśnia, dlaczego mutable.contains () nie działa poprawnie. Wyszukuje obiekt w niewłaściwym wpisie tablicy hash, funkcja equals() nie jest nawet wywoływana. I nic dziwnego, że go nie znajduje.

można zaimplementować hashCode użyciu text.hashCode:

override def hashCode: Int = text.hashCode 
+0

Dokładnie tak, jak mówi Matthew :-) –

+0

Dziękuję, nie zauważyłem, że istnieją specjalne przypadki dla niezmiennych zbiorów wielkości 1. – Stefan

7

Trzeba zastąpić hashCode również. hashCode jest niezbędna do zastąpienia, gdy zastąpisz equals.

Uwaga były też kilka rzeczy, które nie kompiluje, więc edytowany nieco więcej:

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"))) 

Polecam czytanie http://www.artima.com/pins1ed/object-equality.html za dużo uzyskać więcej informacji na ten równość obiektów. To otwiera oko.

+0

Przepraszamy za błędy składni. Następnym razem będę wyglądać dwa razy. – Stefan

+0

Podany powyżej kod działa poprawnie. Przyczyna "dziwnego" zachowania jest taka, jak mówi Mateusz. – Stefan

Powiązane problemy