Chapter 25《The Architecture of Scala Collections》

不同的集合支持实现支持相同的操作,如果在每个集合里面进行单独的实现,会出现大量的代码并且不利于保持集合操作的一致性。新的集合框架主要设计目标就是避免重复,在尽量少的地方定义,并在集合模板中实现大多数操作,由基类和实现灵活继承。


集合构建器

  • 集合的大部分操作都可以通过遍历或者构建器,Traversableforeach方法来实现集合的遍历,通过Builder的实例来构建新的集合。Builder类中主要的方法如下所示:
class Builder[-Elem, +To] {
def +=(elem: Elem): this.type
def result(): To
def clear()
def mapResult[NewTo](f: To => NewTo): Builder[Elem, NewTo] =
 ...
}

Elem是集合中元素的类型,To是生成的集合的类型。使用result()可以从builder中获取到一个集合,之后builder的状态便是未定义的,可以使用clearbuilder的状态重新定义到新的空状态。ArrayBuffer本身就是自己的构造器,使用result会交出本身的buffer


重构共同操作

  • Scala中的集合遵循同样类型的原则,在集合上执行的转换方法必须返回同样类型的集合,比如在List上执行filter,返回的还是List,在Map上执行转换返回的还是MapScala使用所谓的实现特质中的泛型构建器和遍历器来避免代码重复并达成同样类型的目的。这些特质都带有Like后缀,比如说IndexedSeqLikeTraversableLikeTraversableTraversableLike中继承所有的具体方法。这些特质的集合类型以及集合底层元素的类型都是泛化的,比如List[I]rait TraversableLike[+Elem, +Repr] { ... }Elem是底层元素的类型,Repr的类型没有任何限制,它可以被实例化为Traversable之外的类型,比如String或者Array,这些类型可以使用Like特质中的所有操作。
    trait TraversableLike[+Elem, +Repr] {
    def newBuilder: Builder[Elem, Repr] // deferred
    def foreach[U](f: Elem => U)
     // deferred
    ...
    def filter(p: Elem => Boolean): Repr = {
    val b = newBuilder
    foreach { elem => if (p(elem)) b += elem }
    b.result
    }
    }
    
  • TraverableLike中定义了两个抽象方法,newBuilderforeach,用于具体的子类实现。剩下的具体的操作,比如说filter,都是所有的Traversable子类之间的共同操作,在Like中直接实现即可。
  • map的操作稍微复杂一些,因为List[Int]可以map成为List[String],但是上述结构明显是不行的,map需要同样的集合构建器,但是是不同的元素类型,但也可能还和入参类型有关,需要不同的集合构建器。比如说BitSet(1,2,3).map(_.toFloat)最后的类型是Set(1.0,2.0,3.0),因为BitSet中不能使用Float类型。map函数产生的集合类型跟传入的函数有关,还有另外的操作也会出现类似的情况:
    scala> Map("a" -> 1, "b" -> 2) map { case (x, y) => (y, x) }
    res3: scala.collection.immutable.Map[Int,java.lang.String] =
    Map(1 -> a, 2 -> b)
    scala> Map("a" -> 1, "b" -> 2) map { case (x, y) => y }
    res4: scala.collection.immutable.Iterable[Int] =
    List(1, 2)
    

scala解决这个问题的方式是使用重载,不是Java中的重载形式,依然不够灵活,使用的是带有隐式参数的重载,

def map[B, That](f: Elem => B)(implicit bf: CanBuildFrom[Repr, B, That]): That = {
val b = bf(this)
for (x <- this) b += f(x)
b.result
}

CanBuildFrom特质可以生成新的集合构建器,From是旧的集合类型,Elem是新的元素类型,To是新的集合类型。

trait CanBuildFrom[-From, -Elem, +To] {
// Creates a new builder
def apply(from: From): Builder[Elem, To]
}

在每一个集合的伴生对象中都定义了隐式的CanBuildFrom对象用于构建新的集合。隐式转换对map这样的操作提供了正确的静态类型信息,但是运行时的类型信息呢,比如说:

scala> val xs: Iterable[Int] = List(1, 2, 3)
xs: Iterable[Int] = List(1, 2, 3)
scala> val ys = xs map (x => x * x)
ys: Iterable[Int] = List(1, 4, 9)

ys的类型也是List类型,这是通过另外的机制实现的。 CanBuildFromapply方法接收一个collection作为入参,可生成泛型遍历器的许多构建器工厂(事实上除了叶子类的构建器工厂)都会将这个apply请求转发至collectiongenericBuilder方法。这个方法进而调用该集合定义时的集合构建器,Scala使用静态的隐式解析来解决map的类型问题,使用虚拟分发来选择与这些类型最匹配的运行时类型。


集成新的序列

  • RNAA,T,G,U(继承自Base)的序列组合,可以看做是继承IndexedSeq的子类,如果继承IndexedSeq类,则在IndexedSeq中实现的集合类型全部是IndexedSeq,也就是说RNA(A, T, G, U)调用take(1)方法产生的是IndexedSeq的默认实现是Vector(A),如果要产生RNA(1),则需要对take进行覆写,这样一来,RNA需要重写IndexedSeq中的所有方法,比如droptakeWhile等。另外一种实现的方法是RNA继承IndexedSeqLike[Base, RNA]类,可以进一步的来指定输出的集合类型。要达到这个目的需要重新定义IndexedSeqLike中的集合构建器:
    final class RNA2 private (val groups: Array[Int], val length: Int) extends IndexedSeq[Base] with IndexedSeqLike[Base, RNA2] {
    import RNA2._
    override def newBuilder: Builder[Base, RNA2] = new ArrayBuffer[Base] mapResult fromSeq
    def apply(idx: Int): Base = // as before
    }
    

另外还有一类是map函数以及++,使用map函数目前返回的是Vector而不是RNA

def map[B, That](f: Elem => B)(implicit cbf: CanBuildFrom[Repr, B, That]): That

Elem是目前集合的元素类型,Bmap函数的返回类型,That是生成的集合类型。That的类型是由隐式参数cbf确定的,所以接下来就是在RNA2的伴生对象中添加一个隐式对象即可。伴生对象canBuildFrom[RNA, Base, RNA],不能放在RNA类中,需要放在RNA的伴生对象中。


集成新的集和映射

  • 继承Like类型是为了在对集合或映射进行转换操作之后还能得到较为精确的类型。

集成集合到框架的步骤:

    1. 选择集合是可变的还是不可变的;
    1. 继承合适的基本接口和Like实现接口来获取大部分的集合操作,定义Builder来为转换操作提供最优类型;
    1. 定义canBuildFrom隐式对象来为map等操作提供最优类型。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,686评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,668评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,160评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,736评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,847评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,043评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,129评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,872评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,318评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,645评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,777评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,861评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,589评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,687评论 2 351

推荐阅读更多精彩内容