scala雾中风景(9): List(1,2,3) == Seq(1,2,3) ?

惜朝在来往的扎堆里问:

scala> List(1, 1, 2) == Seq(1, 1, 2)
res219: Boolean = true

scala里Seq和List是一会儿事?

这个问题归根到底在于 == 在集合里是怎么实现的?在scala里==的语义等同于java里的equals,我们跟踪一下

val a = List(1,2,3)
val b = Seq(1,2,3)
a.equals(b)     // 设置断点

注意,在a.equals(b)出设置断点,scala-ide不一定能进入内部逻辑,你还是需要在它父类equals方法内设置断点才行。

上面的equals实际会到 GenSeqLike.equals,见下图:

它的逻辑是判断两个集合是否“可比较”(canEqual),如果可比较,则判断内部的元素是否相同:

override def equals(that: Any): Boolean = that match {
    case that: GenSeq[_] => (that canEqual this) && (this sameElements that)
    case _               => false
}   

于是我们推测 List(1,2,3)Seq(1,2,3)的容器类型应该是相同的类型或有继承关系,但是进入到canEqual逻辑内部,无法验证这个判断,它直接返回true,按理说应该对两个容器的类型进行比较一下才合适(看来只是给用户实现的集合类型实现equals时留了一个扩展点,scala自己的集合类型并不做类型判断)。

接下来进入 sameElements 逻辑,因为List混入了LinearSeqOptimized特质,这块的逻辑是在LinearSeqOptimized中的,见下图:

我们看到它通过模式匹配,要求目标集合也必须是LinearSeq类型。然后迭代并比较了两个容器内的各个元素是否相同,都相同的话就认为两个容器也相同。不过从这里的逻辑我们也可以判断出来,两个容器equals为true的话,并不一定需要是完全同样的类型或者有父子关系。我们验证一下:

1)两个集合分别是Seq特质与Set特质下的子类,是两种不一样的集合

scala> List(1,2,3) == Set(1,2,3)
res28: Boolean = false

2) 两个集合都是Set特质下的子类

scala> HashSet(1,2,3) == TreeSet(1,2,3)
res29: Boolean = true

3) 两个集合都是Seq特质下的子类

scala> ListBuffer(1,2) == LinkedList(1,2)
res20: Boolean = true

4) 两个集合都是Seq特质下的子类,不过QueueLinearSeq下的,而RangeIndexedSeq下的

scala> Queue(1,2) == Range(1,2)
res18: Boolean = false

5) 两个集合都是Seq特质下的子类,Seq(1,2,3)的实现是?

scala> Range(1,2,3) == Seq(1,2,3)
res12: Boolean = false

第1种情况好理解,ListSet 毕竟是另种含义不同集合,Set的实现也不会是LinearSeq特质的,所以返回false.第2种也容易理解,两个集合都是Set特质下的。

问题是3,4,5,为何Seq下会有多种情况,这还要我们再全面的看一下scala的集合框架,借用
这里的图片:

正是因为 Seq 特质下,又分为了IndexedSeqLinearSeq 两个分支,并且这两个特质中各自对 sameElements的逻辑有不同的实现,使得IndexedSeq的集合与LinearSeq下的集合比较时不可能相等。

另,对于 List(1,2,3)Seq(1,2,3)在构造集合的背后逻辑,可以参考这篇:通过List.apply方法构造List的背后逻辑

scala雾中风景(9): List(1,2,3) == Seq(1,2,3) ?》上有3个想法

  1. 第4个例子中,Range(1, 2) 最后的值是Range(1) ,肯定不会和Queue(1, 2)
    scala> Range(1, 3) == Queue(1, 2)
    res0 : Boolean = true
    第5个例子中,Range (1, 2, 3) 与 Vector (1, 2, 3) 肯定不会相等,它们的值不一样,
    scala> Range(1, 4) == Vector(1, 2, 3)
    res1 : Boolean = true
    我觉得这样写更能表示同一个Seq特质下的子类相等比较。
    以上个人建议。但还是向楼主学到了许多。

  2. 今天看到这篇blog,也可以参考:http://blog.bruchez.name/2013/05/scala-array-comparison-without-phd.html

发表评论

电子邮件地址不会被公开。 必填项已用*标注