2011-09-25 21 views
10

que he descubierto un comportamiento extraño para los conjuntos mutables que no puedo entender:Scala: Contiene en conjuntos mutables e inmutables

Tengo un objeto que quiero añadir a un conjunto. El método equals para la clase está anulado. Cuando agrego dos objetos diferentes al conjunto, que produce la misma salida para el método igual, obtengo un comportamiento diferente entre los conjuntos mutables e inmutables para el método contains.

Aquí es el fragmento de código:

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

Esto produce como salida:

Set(test) 
false 
Set(test) 
true 

En mi opinión ambas llamadas de contiene debe producir la misma salida (verdadero).

¿Alguien podría ayudarme a entender la diferencia aquí o es esto un error en la implementación del conjunto de scala inmutable? Por cierto, uso scala 2.8.1.final

Gracias.

Respuesta

23

Regla 1 al implementar equals(): Implementar hashCode() al mismo tiempo. Ver Overriding equals and hashCode in Java

En el primer ejemplo, está creando un conjunto mutable, que llama a hashCode para configurar la tabla hash.

En el segundo, está utilizando un conjunto inmutable con una entrada, por lo que Scala en realidad usa una versión optimizada de Set llamada Set1. Set1.contains() simplemente compara la entrada con el elemento pasado usando equals() directamente. Esto se parece a:

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

No se llama hashCode. También hay un Set2, Set3 y Set4.

Así que si cambiamos el código sea:

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

añadir un código hash y una println en los iguales, y la salida es:

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

que explica por qué los mutable.contains () no está funcionando correctamente. Está buscando el objeto en la entrada incorrecta de la tabla hash, equals() ni siquiera se llama. Y, como era de esperar, no lo encuentra.

Puede implementar hashCode usando text.hashCode:

override def hashCode: Int = text.hashCode 
+0

Exactamente como dice Matthew :-) –

+0

Gracias, no he reconocido, que hay casos especiales para conjuntos inmutables de tamaño 1. – Stefan

7

Necesita anular hashCode también. hashCode es esencial para anular cuando sobrescribe equals.

Nota también había algunas cosas que no se compilan, por lo que editar un poco más:

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

recomiendo la lectura http://www.artima.com/pins1ed/object-equality.html para mucho más penetraciones en hacer igualdad de objetos. Es revelador.

+0

Disculpe los errores de sintaxis. La próxima vez lo vería dos veces. – Stefan

+0

El código que proporcionó anteriormente funciona bien. La razón del comportamiento "extraño" es como dice Mateo. – Stefan

Cuestiones relacionadas