覆盖Pair的equals()方法

时间:2010-09-25 09:00:43

标签: scala

之前在scala-user邮件列表中询问了此问题,但没有确认答案。

scala> val T = new Pair(1, 2){
override def equals(obj:Any) = obj.isInstanceOf[(Int, Int)] && obj.asInstanceOf[(Int, Int)]._1 == this._1}
           }

T: (Int, Int) = (1,2)

scala> T match {
        case (1, 1) => println("matched")
        case _ => println("not matched")
   }

not matched


scala> (1, 1) match {
              case T => println("matched")
              case _ => println("not matched")
          }

not matched

scala> T == (1, 1)
res15: Boolean = true

我认为常量(val)模式匹配结果取决于“等于”的返回值,但结果表明情况并非如此,那么标准是什么?

有人建议case (1, 1) =>是提取器模式,而是使用Tuple2.unapply。所以我尝试了这些:

scala> Pair.unapply(T)
res1: Option[(Int, Int)] = Some((1,2))

scala> Pair.unapply(T).get == (1, 1)
res2: Boolean = true

任何人都可以解释为什么==成真,但我不能让它们匹配?

4 个答案:

答案 0 :(得分:13)

您的示例的问题是您只覆盖您定义特定元组的匿名类的equals方法。让我们仔细看看你在运行这里给出的代码时正在做什么。

val p = new Pair(1, 2) {
override def equals(obj:Any) = {
    obj.isInstanceOf[(Int, Int)] && obj.asInstanceOf[(Int, Int)]._1 == this._1
  }
}

Scala在这里做的是创建一个新的匿名类,它扩展Pair并覆盖它的等号。所以这相当于运行以下代码:

class Foo extends Pair(1,2) {
  override def equals(obj:Any) = {
      obj.isInstanceOf[(Int, Int)] && obj.asInstanceOf[(Int, Int)]._1 == this._1
    }
}

val p = new Foo

在这里,您可以确切地看到问题所在! equals的定义不对称。 p == (1,1)评估为true(1,1) == p评估为false!这是因为前者等同于p.equals((1,1)),而后者等同于(1,1).equals(p)。在您给出的示例中,它不起作用,因为案例中的对象与匹配的对象进行比较,而不是相反。因此,如您所示,Pair.unapply(p).get == (1, 1)评估为true,但(1,1) == Pair.unapply(p).get评估为false,而后者似乎是匹配时使用的。{/ p >

但是,在任何情况下,创建一个非对称的等号是一个非常糟糕的想法,因为代码的执行取决于你比较对象的顺序。此外,等于你定义的还有一个问题 - 当您尝试将p 类型为Pair的任何(Int, Int)进行比较时,它会失败并显示错误。这是因为,在类型擦除(这是JVM如何实现泛型)之后,Pair不再被其成分的类型参数化。因此,(Int, Int)(String, String)具有完全相同的类型,因此,以下代码将失败并显示错误:

p == ("foo", "bar")

因为Scala会尝试将(String, String)投射到(Int, Int)

如果你想实现这个功能,你可以做的最简单的事情是使用pimp我的库模式,拉伸Pair。但是,您不应该调用方法equals。称之为其他内容,例如~=。我现在必须要去,但是当我回来时,我可以给你代码。这很容易。您应该查看对中equals的实现,并删除比较第二个参数的部分:)

答案 1 :(得分:3)

我不得不说Scala再一次让我感到惊讶。更多测试显示结果取决于运行上下文。

如果你跑:

object Pair{
    def main(args:Array[String]) {
        val T = new Pair(1, 2){
            override def equals(obj:Any)= obj.isInstanceOf[(Int, Int)] && obj.asInstanceOf[(Int, Int)]._1 == this._1
        }

        (1, 1) match {
            case T => println("matched")
            case _ => println("not matched")
        }
    }

}

你得到:匹配

如果你跑:

object Pair extends Application {
    val T = new Pair(1, 2){
        override def equals(obj:Any)= obj.isInstanceOf[(Int, Int)] && obj.asInstanceOf[(Int, Int)]._1 == this._1
    }

    (1, 1) match {
        case T => println("matched")
        case _ => println("not matched")
    }
}

你得到:不匹配

我一直认为main()方法中的代码与扩展Application trait的对象体中的代码没有区别。真的很奇怪。

答案 2 :(得分:2)

元组在模式匹配器中具有特权。他们不是你的日常课程。这是规范,第8.1.7节“元组模式”。

当你说

(1, 1) match { case T ...

然后在(1,1)上调用equals,这当然表示不,谢谢,不等于。

当你说

T match { case (1, 1) => ...

然后由于元组模式而忽略了你的equals方法,并且T._1被比较为1而T._2被比较为1,并且它再次不匹配。

答案 3 :(得分:2)

#3888的分辨率下,我可以对这个问题给出最终答案。

  1. T match { case (1, 1) =>

    不,它与unapply无关。正如临时提到的那样,case (1,1) =>是一个'Tuple Pattern',是案例类Tuple2的'Constructor Pattern'的别名,它只匹配构造为Tuple2(1,1)或Pair(1,1)的值。

    真正关心unapply的是'Extractor Pattern':

    object Pair {
        val T = new Pair(1,1){
            def unapply(p:(Int, Int)) :Boolean = this._1 == p._1
        }
        def main(args: Array[String]) = {
            (1, 2) match {
                case T() => println("matched")
                case _ => println("not matched")
            }
        } 
    }
    

    你得到“匹配”。请注意case子句中的()

  2. (1, 1) match { case T => ...

    根据scala规范第8.1.5节,这是一个'稳定标识符模式',case T匹配任何值v,使得T == v。所以我们应该得到“匹配”。 “不匹配”的结果 只是由当前编译器实现中的错误引起的。