扩展Scala集合的简单示例

时间:2014-06-19 07:32:01

标签: scala inheritance scala-collections

我正在寻找一个非常简单的子类化Scala集合的例子。我对这一切是如何以及为何有效的全面解释并不感兴趣;互联网上有很多可用的hereelsewhere。我想知道这样做的简单方法。

下面的课程可能是一个尽可能简单的例子。这个想法是,创建一个Set[Int]的子类,它有一个额外的方法:

class SlightlyCustomizedSet extends Set[Int] {
  def findOdd: Option[Int] = find(_ % 2 == 1)
}

显然这是错误的。一个问题是没有构造函数可以将内容放入Set。必须构建CanBuildFrom对象,最好通过调用一些已知的库代码来知道如何构建它。我已经看到在配对对象中实现几个附加方法的示例,但它们显示了它是如何工作的或如何做更复杂的事情。我想看看如何利用库中已有的东西来解决这几行代码问题。实现这一目标的最简单,最简单的方法是什么?

1 个答案:

答案 0 :(得分:19)

如果您只想向类中添加单个方法,那么子类化可能不是最佳选择。 Scala的集合库有点复杂,并且叶子类并不总是适合子类化(可以从子类化HashSet开始,但这会让你开始深入兔子洞的旅程。)

实现目标的一种更简单的方法可能是:

implicit class SetPimper(val s: Set[Int]) extends AnyVal {
  def findOdd: Option[Int] = s.find(_ % 2 == 1)
}

这实际上并不是Set的子类,而是创建了一个隐式转换,允许您执行以下操作:

Set(1,2,3).findOdd // Some(1)

沿着兔子洞

如果您来自Java背景,那么扩展标准集合可能会非常令人惊讶 - 在所有Java标准库充满j.u.ArrayList子类之后,几乎任何可以包含其他内容的东西都是如此。但是,Scala有一个关键的区别:它的首选集合都是不可变的。

这意味着他们没有add方法可以就地修改它们。相反,他们使用+方法构建一个新实例,包含所有原始项目以及新项目。如果他们天真地实现这一点,那就非常低效,因此他们使用各种特定于类的技巧来允许新实例与原始实例共享数据。 +方法甚至可以返回与原始类型不同的对象 - 一些集合类对小集合或空集合使用不同的表示。

但是,这也意味着如果你想继承其中一个不可变集合,那么你需要理解你正在子类化的类的内容,以确保你的子类实例的构造方式与基类。

顺便说一句,如果你想要对可变集合进行子类化,那么这些都不适用于你。他们被视为scala世界中的二等公民,但他们add个方法,很少需要构建新实例。以下代码:

class ListOfUsers(users: Int*) extends scala.collection.mutable.HashSet[Int] {
  this ++= users

  def findOdd: Option[Int] = find(_ % 2 == 1)
}

在大多数情况下(map可能会或多或少地达到预期效果(CanBuildFrom并且朋友可能做得不怎么样,因为我会在Set内找到import scala.collection.SetLike import scala.collection.mutable.Builder import scala.collection.generic.CanBuildFrom class UserSet(delegate: Set[Int]) extends Set[Int] with SetLike[Int, UserSet] { override def contains(key: Int) = delegate.contains(key) override def iterator = delegate.iterator override def +(elem: Int) = new UserSet(delegate + elem) override def -(elem: Int) = new UserSet(delegate - elem) override def empty = new UserSet(Set.empty) override def newBuilder = UserSet.newBuilder override def foreach[U](f: Int => U) = delegate.foreach(f) // Optional override def size = delegate.size // Optional } object UserSet { def apply(users: Int*) = (newBuilder ++= users).result() def newBuilder = new Builder[Int, UserSet] { private var delegateBuilder = Set.newBuilder[Int] override def +=(elem: Int) = { delegateBuilder += elem this } override def clear() = delegateBuilder.clear() override def result() = new UserSet(delegateBuilder.result()) } implicit object UserSetCanBuildFrom extends CanBuildFrom[UserSet, Int, UserSet] { override def apply() = newBuilder override def apply(from: UserSet) = newBuilder } } 内容。一分钟,但请忍受我。)

核选项

如果继承失败了,我们总会有一个核选择:构成。我们可以创建自己的CanBuildFrom子类,将其职责委托给委托,如下:

map

这可以说太复杂,太简单了。这比我们想写的代码行多得多,然而,它仍然非常天真。

没有伴侣班就可以使用,但如果没有SetSet将返回普通CanBuildFrom,这可能不是您所期望的。我们还重写了empty建议我们实施的文档的可选方法。

如果我们是彻底的,我们已经创建了一个case class UserSet(users: Set[Int]) ,并为我们的可变类实现了{{1}},因为这可以确保创建新实例的少数几个方法可以像我们期望的那样工作

但这听起来像很多工作......

如果这听起来太多了,请考虑以下内容:

{{1}}

当然,你必须输入几个字母来获取用户集,但我认为它比子类更好地区分了问题。