【问题标题】:Scala: Contains in mutable and immutable setsScala:包含可变和不可变集合
【发布时间】:2011-11-24 18:04:47
【问题描述】:

我发现了一个我无法理解的可变集合的奇怪行为:

我有一个想要添加到集合中的对象。类的 equals 方法被覆盖。当我将两个不同的对象添加到集合中时,这会为 equals 方法生成相同的输出,但对于 contains 方法,我会在可变集和不可变集之间获得不同的行为。

这里是sn-p的代码:

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

在我看来,contains 的两个调用都应该产生相同的输出(true)。

谁能帮我理解这里的区别,或者这是 scala 不可变集实现中的一个错误?对了,我用的是scala 2.8.1.final

谢谢。

【问题讨论】:

    标签: scala set immutability scala-collections mutable


    【解决方案1】:

    实现equals()时的规则1:同时实现hashCode()。见Overriding equals and hashCode in Java

    在第一个示例中,您正在创建一个可变集,它调用 hashCode 来设置哈希表。

    在第二种情况下,您使用的是带有一个条目的不可变集合,因此 Scala 实际上使用了称为 Set1 的 Set 的优化版本。 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")))
    

    在equals中添加一个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
    

    【讨论】:

    • 谢谢,我还没有意识到,大小为 1 的不可变集合有特殊情况。
    【解决方案2】:

    您还需要覆盖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 以获得更多关于对象相等的见解。让人大开眼界。

    【讨论】:

    • 您在上面提供的代码工作正常。 “奇怪”行为的原因正如马修所说。
    猜你喜欢
    • 2012-01-07
    • 2013-08-30
    • 1970-01-01
    • 1970-01-01
    • 2017-05-21
    • 1970-01-01
    • 2016-10-02
    • 1970-01-01
    • 2023-03-18
    相关资源
    最近更新 更多