Scala:在Scala集合中,Traversable和Iterable特质有什么区别?

107

我查看了这个问题,但仍然不明白Iterable和Traversable特质之间的区别。有人能解释一下吗?


2
Scala 2.13中已经没有Traversable了(它仍然作为Iterable的弃用别名保留到2.14)。 - Xavier Guihot
4个回答

235

将其视为吹和吸之间的区别。

当您调用Traversableforeach或其衍生方法时,它会逐个将其值传递到您的函数中-因此它控制着迭代。

但通过由Iterable返回的Iterator,您可以从中提取值,并自行控制何时移动到下一个值。


62
人们过去常用“推”和“拉”的说法,而不是“吹”和“吸”,但我欣赏你的开放态度。 - Martijn
3
下次面试时,不要忘记这个。 - thestephenstanton

131

简单来说,迭代器保留状态,可遍历对象则不保留。

Traversable(可遍历)有一个抽象方法:foreach。当你调用foreach方法时,这个集合会依次将它所保存的所有元素传递给传入的函数。

另一方面,Iterable(可迭代)则有一个抽象方法iterator,返回一个Iterator(迭代器)。你可以在任意时间调用Iteratornext方法,以获取下一个元素。在你调用next方法之前,迭代器必须跟踪它在集合中的位置及后续还有哪些元素。


4
但是 Iterable 扩展自 Traversable,所以我猜你指的是那些不是 IterableTraversable - Robin Green
4
@RobinGreen 我的意思是遵循 Traversable 接口不需要保持状态,而遵循 Iterator 接口则需要。 - Daniel C. Sobral
10
实现了Iterable接口的可遍历对象并不保留任何遍历状态,而是由实现Iterable接口的对象创建并返回一个Iterator对象来保存状态。 - Graham Lea
4
值得注意的是,从2.13版本开始,Traversable特质已被弃用。引用Stefan Zeiger的话:“在当前的库中,Traversable抽象没有承担其重要性,很可能不会出现在新设计中。我们想做的一切都可以用Iterable表达。” - Igor Urisman

26

tl;dr Iterables 是能够产生有状态的 IteratorsTraversables

首先要知道,IterableTraversable 的子特质。

其次,

  • Traversable 需要实现 foreach 方法,它被其他方法使用。

  • Iterable 需要实现 iterator 方法,它被其他方法使用。

例如,Traversablefind 实现使用 foreach(通过 for 推导)并抛出 BreakControl 异常来停止迭代,一旦找到满意的元素。

trait TravserableLike {
  def find(p: A => Boolean): Option[A] = {
    var result: Option[A] = None
    breakable {
      for (x <- this)
        if (p(x)) { result = Some(x); break }
    }
    result
  }
}

相比之下,Iterablesubtract 方法会覆盖这个实现,并在 Iterator 上调用 find 方法,一旦找到元素就停止迭代:

trait Iterable {
  override /*TraversableLike*/ def find(p: A => Boolean): Option[A] =
    iterator.find(p)
}

trait Iterator {
  def find(p: A => Boolean): Option[A] = {
    var res: Option[A] = None
      while (res.isEmpty && hasNext) {
        val e = next()
        if (p(e)) res = Some(e)
      }
    res
  }
}

不向Traversable迭代抛出异常是一个好方法,但这是在仅使用foreach时部分迭代的唯一方法。

从某种角度来看,Iterable是更具要求/强大的特性,因为您可以轻松地使用iterator实现foreach,但您无法真正使用foreach实现iterator


总之,Iterable通过有状态的Iterator提供了一种暂停、恢复或停止迭代的方式。对于Traversable,它要么全部执行(除了流程控制异常),要么不执行。

大多数情况下,这并不重要,您将需要更通用的接口。但如果您需要更多定制化的迭代控制,则需要一个Iterator,您可以从一个Iterable中检索到它。


1

Daniel的回答听起来不错。让我试着用自己的话来解释一下。

所以,一个Iterable可以给你一个迭代器,让你逐个遍历元素(使用next()),并随时停止和继续。为了做到这一点,迭代器需要保持对元素位置的内部“指针”。但是Traversable通过提供foreach方法来一次性遍历所有元素,而不需要停止。

像Range(1,10)这样的东西只需要作为Traversable拥有2个整数状态。但是,Range(1,10)作为Iterable会给你一个迭代器,需要使用3个整数作为状态,其中之一是索引。

考虑到Traversable还提供了foldLeft、foldRight,它的foreach需要按照已知和固定的顺序遍历元素。因此,可以为Traversable实现一个迭代器。例如: def iterator = toList.iterator


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接