接上一篇,这个问题还没完,泽斌给出这个问题是因另一个问题引发的:Scala 的 for each,为什么会有且仅有一次 recursion? 引用原问题:
Scala 2.10/2.11 中,运行
val l = MutableList(16)
for (i<-l) {
val p = i/2
if (!(l contains p)) {
l += p
}
}
l
得到的是
MutableList[Int](16, 8, 4)
如果是返回 (16, 8)(如同 JavaScript 或 PHP)的话我能理解。或者是返回 (16, 8, 4, 2, 1, 0)(如同 Python 或 Ruby) 我也能够理解。但是返回 (16, 8, 4) 是为什么呢?
这得看看MutableList
的迭代器实现了, 它在scala.collection.LinearSeqOptimized
里面:
override /*IterableLike*/
def foreach[B](f: A => B) {
var these = this
while (!these.isEmpty) {
f(these.head)
these = these.tail
}
}
不太容易直接看出来这块的问题,断点的时候发现这里会导致调试器在展示值时调用toString引起异常,我是复制了一份MutableList
的代码在foreach
里增加一些打印信息发现的。
我们在repl下模拟这个迭代过程,刚开始foreach的时候元素只有1个,对16进行处理,追加一个新元素8:
scala> val l = scala.collection.mutable.MutableList(16)
l: scala.collection.mutable.MutableList[Int] = MutableList(16)
scala> l += 8
res0: l.type = MutableList(16, 8)
这个追加的行为对应foreach
里面的f(these.head)
这一句,之后把指针指向后续链表节点:
scala> var these = l.tail
these: scala.collection.mutable.MutableList[Int] = MutableList(8)
因为刚开始var these=this
,所以这次these=these.tail
会得到链表的第二个元素8,然后传入8,原链表又被追加了一个元素4:
scala> l += 4
res1: l.type = MutableList(16, 8, 4)
接着继续移动指针,但这次these
对象已经不是this
而是单节点链表,取它的tail会得到空,导致foreach
终止。所以最终原链表里有3个元素:
scala> these = these.tail
these: scala.collection.mutable.MutableList[Int] = MutableList()
虽然,这里的these
与原链表是复用的,它size虽然为0,但实际可以访问后续元素:
scala> these(0)
res3: Int = 4
我觉得应该算是MutableList
迭代器的bug了,它不应该使用LinearSeqOptimized
里的实现,那个里面的逻辑没有考虑到集合长度可变的情况。