Scala 集合(四)
Scala Builder、CanBuildFrom和Like特質
1.Builder
我在前面幾篇有關集合的文章中曾提到,如果小心合理地使用,可變集合將是為了提高效能而做出的合理讓步。事實上,集合API 在內部使用了可變集合建立新的輸出集合,如在map 操作中就是如此。map 操作還使用了collection.mutable.Builder 特徵的實現,以構造新例項。
原始碼:
package scala.collection.mutable trait Builder[-Elem, +To] extends scala.AnyRef with scala.collection.generic.Growable[Elem] { def +=(elem : Elem) : Builder.this.type def clear() : scala.Unit def result() : To def sizeHint(size : scala.Int) : scala.Unit = { /* compiled code */ } def sizeHint(coll : scala.collection.TraversableLike[_, _]) : scala.Unit = { /* compiled code */ } def sizeHint(coll : scala.collection.TraversableLike[_, _], delta : scala.Int) : scala.Unit = { /* compiled code */ } def sizeHintBounded(size : scala.Int, boundingColl : scala.collection.TraversableLike[_, _]) : scala.Unit = { /* compiled code */ } def mapResult[NewTo](f : scala.Function1[To, NewTo]) : scala.collection.mutable.Builder[Elem, NewTo] = { /* compiled code */ } }
這種罕見的Builder.this.type 簽名是一個單例型別(singleton type)。它確保+= 方法在呼叫時只返回生Builder 的例項,也就是this。如果嘗試返回Builder 的一個新例項,那麼就無法通過型別檢查。
接下來做一個List 的builder 實現:
package cn.com.tengen.test.obj import collection.mutable.Builder class ListBuilder[T] extends Builder[T,List[T]]{ private var storage = Vector.empty[T] override def +=(elem:T) ={ storage = storage :+ elem this } override def clear(): Unit = { storage = Vector.empty[T] } override def result(): List[T] = storage.toList } object ListBuilder extends App{ val lb = new ListBuilder[Int] (1 to 10) foreach (i => lb += i) println(lb.result) } 輸出結果: List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
2 CanBuildFrom
做一個List呼叫map的例子:
object Test extends App {
var l = List(1, 2, 3, 4, 5, 6)
l = l map (_ + 1)
println(l)
}
我們看一下map:
首先map方法來自於:List
package scala.collection.immutable sealed abstract class List[+A]() extends scala.collection.AbstractSeq[A] ... { ... final override def map[B, That](f : scala.Function1[A, B])(implicit bf : scala.collection.generic.CanBuildFrom[scala.collection.immutable.List[A], B, That]) : That = { /* compiled code */ } ... }
結果發現map是被重寫的,追根溯源
scala.collection.AbstractSeq[A] -> scala.collection.AbstractIterable[A] -> scala.collection.AbstractTraversable[A] -> scala.AnyRef with scala.collection.Traversable[A] -> scala.AnyRef with scala.collection.TraversableLike[A, scala.collection.Traversable[A]]
一層層的繼承終於發現map源於TraversableLike
package scala.collection
trait TraversableLike[+A, +Repr] extends scala.Any with ... {
...
def map[B, That](f : scala.Function1[A, B])(implicit bf : scala.collection.generic.CanBuildFrom[Repr, B, That]) : That = {
/* compiled code */
}
...
}
scala.collection.immutable.List對map的重寫實質上就是泛型更具體一點,接下來就簡單的探討一下TraversableLike中的map方法:
Repr 是內部用來儲存元素的集合型別。B 是函式f 建立的元素型別。That 是我們想要建立的目標集合的型別引數,它可能與輸入的原始集合相同,也可能不同。
TraversableLike 對其子類(如List)一無所知,但它還是可以構造新的List 並將其返回,因為隱含的CanBuildFrom 例項封裝了所需要的細節。
CanBuildFrom 是建立Builder 例項的工廠的trait,由工廠來完成實際構造新集合的工作。使用CanBuildFrom 技術的一個缺點是額外增加了方法簽名的複雜度。然而,除了促進類似map 等操作在面向物件中的重用以外,CanBuildFrom 在其他方面使得構造更加模組化、通用化。
例如:CanBuildFrom 例項可以為返回的不同具體集合例項化多個Builder。通常它會返回相同型別的新集合,或者返回對給定元素來說更高效的子型別。
實現一個包含很多元素的對映表最好將鍵儲存在散列表中,同時提供均為O(1) 的儲存和查詢複雜度。然而,對於一個小對映,可能將元素直接存在陣列或列表中會更快,這時n 很小,O(n) 的查詢複雜度事實上比散列表的O(1) 複雜度還要快,這取決於散列表的常數開銷因子。
輸入集合型別不能用來作為輸出集合型別的情況還有其他形式。考慮下面的示例:
object Test extends App {
val set = collection.BitSet(1, 2, 3, 4, 5)
println(set.getClass+": "+set)
var s = set map (_ + "a")
println(s.getClass+": "+s)
}
輸出:
class scala.collection.immutable.BitSet$BitSet1: BitSet(1, 2, 3, 4, 5)
class scala.collection.immutable.TreeSet: TreeSet(1a, 2a, 3a, 4a, 5a)
在命令列看一下:
在https://blog.csdn.net/u014646662/article/details/84302437已經介紹了BitSet集合,BitSet是非負整數的集合,Int加上String的結果是String,所以,如果將其元素轉為字串,隱含的CanBuildFrom 只能例項化不同於輸入的輸出集合。
在本例中, 輸出集合是SortedSet。
類似地,對於字串(字元序列),我們也可能遇到以下情形:
CanBuildFrom 的另一個好處是傳送其他上下文資訊的能力,這些上下文資訊可能是原集合不知道或不適合由原集合來傳遞的。例如:使用分散式計算API 時,特定的CanBuildFrom例項可能被用於構建那些適合序列化到遠端程序的集合。
3 Like特質
Builder 和CanBuildFrom 為輸出集合指定了型別引數。為了支援對這些引數的指定,也為了加強程式碼重用,你知道的大多數集合都混入了相應的 *Like 特質。該trait 可以新增合適的返回值型別,併為常見方法提供實現。
以下是collection.immutable.Seq 的宣告:
package scala.collection.immutable
trait Seq[+A] extends scala.AnyRef
with scala.collection.immutable.Iterable[A]
with scala.collection.Seq[A]
with scala.collection.generic.GenericTraversableTemplate[A, scala.collection.immutable.Seq]
with scala.collection.SeqLike[A, scala.collection.immutable.Seq[A]]
with scala.collection.Parallelizable[A, scala.collection.parallel.immutable.ParSeq[A]] {
}
需要注意的是,collection.SeqLike 同時用元素型別A 和Seq[A] 本身進行引數化。第二個引數用於約束在map 等方法中能夠使用的CanBuildFrom 例項。該trait 還實現了Seq 中大部分我們熟悉的方法。
SeqLike 原始碼:
package scala.collection
trait SeqLike[+A, +Repr] extends scala.Any with scala.collection.IterableLike[A, Repr] with scala.collection.GenSeqLike[A, Repr] with scala.collection.Parallelizable[A, scala.collection.parallel.ParSeq[A]] {
protected[this] override def thisCollection : scala.collection.Seq[A] = { /* compiled code */ }
protected[this] override def toCollection(repr : Repr) : scala.collection.Seq[A] = { /* compiled code */ }
def length : scala.Int
def apply(idx : scala.Int) : A
protected[this] override def parCombiner : scala.collection.parallel.Combiner[A, scala.collection.parallel.ParSeq[A]] = { /* compiled code */ }
def lengthCompare(len : scala.Int) : scala.Int = { /* compiled code */ }
override def isEmpty : scala.Boolean = { /* compiled code */ }
override def size : scala.Int = { /* compiled code */ }
def segmentLength(p : scala.Function1[A, scala.Boolean], from : scala.Int) : scala.Int = { /* compiled code */ }
def indexWhere(p : scala.Function1[A, scala.Boolean], from : scala.Int) : scala.Int = { /* compiled code */ }
def lastIndexWhere(p : scala.Function1[A, scala.Boolean], end : scala.Int) : scala.Int = { /* compiled code */ }
def permutations : scala.collection.Iterator[Repr] = { /* compiled code */ }
def combinations(n : scala.Int) : scala.collection.Iterator[Repr] = { /* compiled code */ }
def reverse : Repr = { /* compiled code */ }
def reverseMap[B, That](f : scala.Function1[A, B])(implicit bf : scala.collection.generic.CanBuildFrom[Repr, B, That]) : That = { /* compiled code */ }
def reverseIterator : scala.collection.Iterator[A] = { /* compiled code */ }
def startsWith[B](that : scala.collection.GenSeq[B], offset : scala.Int) : scala.Boolean = { /* compiled code */ }
def endsWith[B](that : scala.collection.GenSeq[B]) : scala.Boolean = { /* compiled code */ }
def indexOfSlice[B >: A](that : scala.collection.GenSeq[B]) : scala.Int = { /* compiled code */ }
def indexOfSlice[B >: A](that : scala.collection.GenSeq[B], from : scala.Int) : scala.Int = { /* compiled code */ }
def lastIndexOfSlice[B >: A](that : scala.collection.GenSeq[B]) : scala.Int = { /* compiled code */ }
def lastIndexOfSlice[B >: A](that : scala.collection.GenSeq[B], end : scala.Int) : scala.Int = { /* compiled code */ }
def containsSlice[B](that : scala.collection.GenSeq[B]) : scala.Boolean = { /* compiled code */ }
def contains[A1 >: A](elem : A1) : scala.Boolean = { /* compiled code */ }
override def union[B >: A, That](that : scala.collection.GenSeq[B])(implicit bf : scala.collection.generic.CanBuildFrom[Repr, B, That]) : That = { /* compiled code */ }
def diff[B >: A](that : scala.collection.GenSeq[B]) : Repr = { /* compiled code */ }
def intersect[B >: A](that : scala.collection.GenSeq[B]) : Repr = { /* compiled code */ }
def distinct : Repr = { /* compiled code */ }
def patch[B >: A, That](from : scala.Int, patch : scala.collection.GenSeq[B], replaced : scala.Int)(implicit bf : scala.collection.generic.CanBuildFrom[Repr, B, That]) : That = { /* compiled code */ }
def updated[B >: A, That](index : scala.Int, elem : B)(implicit bf : scala.collection.generic.CanBuildFrom[Repr, B, That]) : That = { /* compiled code */ }
def +:[B >: A, That](elem : B)(implicit bf : scala.collection.generic.CanBuildFrom[Repr, B, That]) : That = { /* compiled code */ }
def :+[B >: A, That](elem : B)(implicit bf : scala.collection.generic.CanBuildFrom[Repr, B, That]) : That = { /* compiled code */ }
def padTo[B >: A, That](len : scala.Int, elem : B)(implicit bf : scala.collection.generic.CanBuildFrom[Repr, B, That]) : That = { /* compiled code */ }
def corresponds[B](that : scala.collection.GenSeq[B])(p : scala.Function2[A, B, scala.Boolean]) : scala.Boolean = { /* compiled code */ }
def sortWith(lt : scala.Function2[A, A, scala.Boolean]) : Repr = { /* compiled code */ }
def sortBy[B](f : scala.Function1[A, B])(implicit ord : scala.math.Ordering[B]) : Repr = { /* compiled code */ }
def sorted[B >: A](implicit ord : scala.math.Ordering[B]) : Repr = { /* compiled code */ }
override def toSeq : scala.collection.Seq[A] = { /* compiled code */ }
def indices : scala.collection.immutable.Range = { /* compiled code */ }
override def view : scala.AnyRef with scala.collection.SeqView[A, Repr] = { /* compiled code */ }
override def view(from : scala.Int, until : scala.Int) : scala.collection.SeqView[A, Repr] = { /* compiled code */ }
override def toString() : _root_.scala.Predef.String = { /* compiled code */ }
}
object SeqLike extends scala.AnyRef {
def indexOf[B](source : scala.collection.Seq[B], sourceOffset : scala.Int, sourceCount : scala.Int, target : scala.collection.Seq[B], targetOffset : scala.Int, targetCount : scala.Int, fromIndex : scala.Int) : scala.Int = { /* compiled code */ }
def lastIndexOf[B](source : scala.collection.Seq[B], sourceOffset : scala.Int, sourceCount : scala.Int, target : scala.collection.Seq[B], targetOffset : scala.Int, targetCount : scala.Int, fromIndex : scala.Int) : scala.Int = { /* compiled code */ }
}
簡單總結一下:
(1) 用Builder 抽象了構造。
(2) 用CanBuildFrom 提供隱含的工廠,用於構造適合給定上下文的生成器例項。
(3) Like 特徵為Builder 和CanBuildFrom 新增必須的返回值型別引數,並提供大部分的方法實現。