Scala: содержится в изменчивых и неизменных наборах - PullRequest
10 голосов
/ 25 сентября 2011

Я обнаружил странное поведение для изменчивых множеств, которое я не могу понять:

У меня есть объект, который я хочу добавить в набор. Метод equals для класса переопределяется. Когда я добавляю два разных объекта в набор, который выдает один и тот же вывод для метода equals, я получаю различное поведение между изменяемыми и неизменяемыми наборами для метода contains.

Вот фрагмент кода:

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

По моему мнению, оба вызова содержимого должны давать одинаковый результат (true)

Может ли кто-нибудь помочь мне понять разницу здесь или это ошибка в реализации неизменяемого набора scala? Кстати, я использую scala 2.8.1.final

Спасибо.

Ответы [ 2 ]

23 голосов
/ 25 сентября 2011

Правило 1 при реализации equals (): реализовать hashCode () одновременно.См. Переопределение equals и hashCode в Java

В первом примере вы создаете изменяемый набор, который вызывает hashCode для установки хеш-таблицы.

Вво-вторых, вы используете неизменяемый набор с одной записью, поэтому Scala на самом деле использует оптимизированную версию Set под названием 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)
  }
}

Хеш-код не вызывается.Также есть 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 в equals, и получим:

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

, который объясняет, почему mutable.contains () не работает правильно.Он ищет объект в неправильной записи хеш-таблицы, equals () даже не вызывается.И, что неудивительно, он не находит его.

Вы можете реализовать hashCode, используя text.hashCode:

override def hashCode: Int = text.hashCode
7 голосов
/ 25 сентября 2011

Вам также необходимо переопределить hashCode.hashCode необходимо переопределить при переопределении equals.

Обратите внимание, что было также несколько вещей, которые не компилировались, поэтому я отредактировал немного больше:

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, чтобы узнать большеделать объектное равенство.Это открытие глаза.

...