前序
之前探究集合的函式式Api時發現,這些函式都會遍歷集合並且提早建立新集合,每一步的中間結果會被儲存在新集合中。當資料量很大時,呼叫十分的低效。
fun main(args: Array<String>) {
val list = (1..10).toList()
list.filter {
it % 2 == 0
}.map {
it * it
}.forEach {
println(it)
}
}
複製程式碼
序列
序列對每個元素逐個執行所有處理步驟,可以避免構建中間變數,提高整個集合處理鏈的效能。序列也稱為惰性集合,序列與Java8中的Stream很像,序列是Kotlin對流這種概念提供的實現。
Kotlin惰性集合操作的入口是 Sequence
介面。該介面只有 iterator
方法,用來從序列中獲取值。
public interface Sequence<out T> {
public operator fun iterator(): Iterator<T>
}
複製程式碼
建立序列
建立序列有四種方式:
- 1、使用頂層函式
sequenceOf()
,將元素作為其引數。(類似建立集合的那一堆頂層函式,如listOf)
val numbers= sequenceOf(1,2,3,4,5,6,7,8,9,10)
複製程式碼
- 2、使用Iterable的擴充套件函式
asSequence()
將集合轉換為序列。(常用)
val numbers = (1..10).toList().asSequence()
複製程式碼
- 3、使用
generateSequence()
。給定一個初識的元素,並提供函式計算下一個元素。該函式會一直生成序列的元素,直到函式實參返回null為止。如果函式實參不返回null,則該序列將是一個無限序列:
val numbers = generateSequence(6){
it + 2
}
複製程式碼
使用generateSequence()
提供有限序列:
val numbers = generateSequence(6){
if (it < 10)
it + 2
else
null
}
複製程式碼
- 4、使用
sequence()
函式.該函式接收一個函式型別為SequenceScope<T>.() -> Unit
的實參。可以在傳遞給sequence()
函式的lambda表示式中使用SequenceScope
物件的 yield() 和 yieldAll() 新增序列元素。yield()用於新增單個序列元素; yieldAll()用於將列表或序列中的元素轉化為新序列的元素。
val numbers = sequence{
yield(1)
yieldAll(listOf(2,3))
yieldAll(setOf(4,5))
yieldAll(generateSequence(6){
if (it < 10)
it + 1
else
null
})
}
複製程式碼
中間操作和終端操作
序列一樣可以像集合一樣呼叫函式式Api,但序列的操作分為兩大類:中間操作和終端操作。
中間操作的定義:中間操作始終是惰性的,中間操作返回的是另一個序列。
可以通過函式的返回資訊,判斷是否為中間操作:
//filter函式,返回Sequence<T>,中間操作。
//注意這是一個帶Sequence<T>接收者的函式型別引數!!
public fun <T> Sequence<T>.filter(predicate: (T) -> Boolean): Sequence<T> {
return FilteringSequence(this, true, predicate)
}
//map函式,返回Sequence<T>,中間操作
//注意這是一個帶Sequence<T>接收者的函式型別引數!!
public fun <T, R> Sequence<T>.map(transform: (T) -> R): Sequence<R> {
return TransformingSequence(this, transform)
}
複製程式碼
惰性怎麼理解呢?執行以下例子:
val list = (1..10).toList()
list.asSequence()
.filter {
println("filter $it")
it % 2 == 0
}.map {
println("map $it")
it * it
}
複製程式碼
結果是並無任何列印,表示filter和map函式被"延遲"了,只有配合末端操作求結果時,中間操作的才被觸發。
末端操作定義:觸發執行所有的延期計算(指中間操作),並返回一個結果,結果可能是集合、數字等。
//forEach函式,返回值不是序列,末端操作
//注意這是一個帶Sequence<T>接收者的函式型別引數!
public inline fun <T> Sequence<T>.forEach(action: (T) -> Unit): Unit {
for (element in this) action(element)
}
//count函式,返回值不是序列,末端操作
//注意這是一個帶Sequence<T>接收者的函式型別引數!
public inline fun <T> Sequence<T>.count(predicate: (T) -> Boolean): Int {
var count = 0
for (element in this) if (predicate(element)) checkCountOverflow(++count)
return count
}
複製程式碼
中間操作為什麼是惰性的
估計很多小夥伴應該和我一樣,很好奇為什麼中間操作是惰性的?想要得到答案,那就只能去檢視原始碼進行分析了,先看asSequence():
public fun <T> Iterable<T>.asSequence(): Sequence<T> {
return Sequence { this.iterator() }
}
public inline fun <T> Sequence(crossinline iterator: () -> Iterator<T>): Sequence<T> = object : Sequence<T> {
override fun iterator(): Iterator<T> = iterator()
}
複製程式碼
asSequence()函式會建立一個匿名的Sequence匿名類物件,並將集合的迭代器儲存起來,作為自己iterator()方法的返回值。
中間操作
(可以直接跳過程式碼,看結果)
#filter函式
public fun <T> Sequence<T>.filter(predicate: (T) -> Boolean): Sequence<T> {
//返回一個FilteringSequence物件
return FilteringSequence(this, true, predicate)
}
internal class FilteringSequence<T>(
private val sequence: Sequence<T>,
private val sendWhen: Boolean = true,
private val predicate: (T) -> Boolean
) : Sequence<T> {
override fun iterator(): Iterator<T> = object : Iterator<T> {
//獲取上一個序列的迭代器
val iterator = sequence.iterator()
// -1 for unknown, 0 for done, 1 for continue
var nextState: Int = -1
var nextItem: T? = null
//計算該中間操作的實現(簡單說就是在)
private fun calcNext() {
while (iterator.hasNext()) {
val item = iterator.next()
//執行謂詞lambda,判斷是否符合條件
if (predicate(item) == sendWhen) {
//符合條件則獲取元素
nextItem = item
//並修改狀態
nextState = 1
return
}
}
nextState = 0
}
override fun next(): T {
//檢查機制
if (nextState == -1)
calcNext()
if (nextState == 0)
throw NoSuchElementException()
//獲取值,並將狀態重置
val result = nextItem
nextItem = null
nextState = -1
@Suppress("UNCHECKED_CAST")
//返回值
return result as T
}
override fun hasNext(): Boolean {
//在上一個序列的迭代器的基礎上,進行謂詞運算,判斷是否有下一個
if (nextState == -1)
calcNext()
return nextState == 1
}
}
}
複製程式碼
#map函式
public fun <T, R> Sequence<T>.map(transform: (T) -> R): Sequence<R> {
return TransformingSequence(this, transform)
}
internal class TransformingSequence<T, R>
constructor(private val sequence: Sequence<T>, private val transformer: (T) -> R) : Sequence<R> {
override fun iterator(): Iterator<R> = object : Iterator<R> {
//獲取上一個序列的迭代器
val iterator = sequence.iterator()
override fun next(): R {
//用函式型別引數進行運算,返回值
return transformer(iterator.next())
}
override fun hasNext(): Boolean {
//沿用上一個序列的迭代器的hasNext()函式
return iterator.hasNext()
}
}
internal fun <E> flatten(iterator: (R) -> Iterator<E>): Sequence<E> {
return FlatteningSequence<T, R, E>(sequence, transformer, iterator)
}
}
複製程式碼
結合其他中間操作的程式碼得到的結果是:
- 1、中間操作都會獲取上一個序列(因為是帶序列接收者的lambda)的迭代器。
- 2、自身實現Sequence介面所獲得的iterator()函式,將返回一個匿名的迭代器物件。
- 3、自身的迭代器物件的hasNext()函式將呼叫上一個序列的迭代器的hasNext()函式,或在上一個序列的迭代器的基礎上進行封裝。
- 4、自身的迭代器物件的next()函式,將呼叫該中間操作所接收的函式型別引數進行運算,最後返回一個值。
- 總的來說,中間操作只是對迭代器的一層層封裝,而內部並無使用while或for進行迭代。
末端操作
(可以直接跳過程式碼,看結果)
#forEach函式
public inline fun <T> Sequence<T>.forEach(action: (T) -> Unit): Unit {
//迭代進行(注意該this,是指最後一箇中端操作返回的Sequence物件)
for (element in this)
action(element)
}
複製程式碼
#count函式
public inline fun <T> Sequence<T>.count(predicate: (T) -> Boolean): Int {
var count = 0
//迭代進行(注意該this,是指最後一箇中端操作返回的Sequence物件)
for (element in this)
if (predicate(element))
checkCountOverflow(++count)
return count
}
複製程式碼
結合其他末端操作的程式碼得到的結果是:
- 1、末端操作使用中間操作返回Sequence物件(因為末端操作也是帶序列接收者的lambda)獲取迭代器,用於forEach迴圈中。(這就解析了為什麼要加末端操作才能是中間操作被執行,因為只有在forEach中迭代器才能被使用。這時,中間操作的返回值才能從迭代器的next()中返回。)
- 2、在forEach中對每一個元素作為值傳給函式型別的引數進行運算。
整體流程如下所示:
如果在Java的角度上看,就更好理解了。先看一波反編譯程式碼:
public static final void main(@NotNull String[] args) {
List list = CollectionsKt.listOf(new Integer[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10});
Sequence $this$forEach$iv = SequencesKt.map(SequencesKt.filter(CollectionsKt.asSequence((Iterable)list), (Function1)null.INSTANCE), (Function1)null.INSTANCE);
int $i$f$forEach = false;
Iterator var4 = $this$forEach$iv.iterator();
while(var4.hasNext()) {
Object element$iv = var4.next();
int it = ((Number)element$iv).intValue();
int var7 = false;
boolean var8 = false;
System.out.println(it);
}
}
複製程式碼
提取重點程式碼(1):
//(1)將中間操作對呀的Sequence例項巢狀建立,得到最後一箇中間操作的Sequence物件
Sequence $this$forEach$iv = SequencesKt.map(SequencesKt.filter(CollectionsKt.asSequence((Iterable)list), (Function1)null.INSTANCE), (Function1)null.INSTANCE);
複製程式碼
將這行程式碼簡化:
Sequence listToSequence = CollectionsKt.asSequence((Iterable)list)
Sequence filterSequence = SequencesKt.filter(listToSequence,(Function1)null.INSTANCE)
Sequence mapSequence = SequencesKt.map(filterSequence,,(Function1)null.INSTANCE)
Sequence $this$forEach$iv = mapSequence
複製程式碼
可以看到,各個中間操作都會產生的Sequence物件,都按照其呼叫的順序進行巢狀,最後得到最後一箇中間操作的Sequence物件。
提取重點程式碼(2):
//獲取最後一箇中間操作的Sequence物件
Iterator var4 = $this$forEach$iv.iterator();
//末端操作迭代迭代器,呼叫迭代器的next()方法時,將按照中間操作巢狀的瞬間執行中間操作對應的迭代器next方法,得到中間操作的返回值
//。最後一箇中間操作的返回值交由末端操作處理
while(var4.hasNext()) {
Object element$iv = var4.next();
//..
}
複製程式碼
末端操作的for迴圈會變成while迴圈,但還是依據迭代器進行迭代。迭代過程中不斷呼叫各個中間操作的迭代器,執行中間操作,最後將中間操作得到的值交由末端操作進行處理。
總結
Kotlin的序列使用裝飾設計模式,對集合轉換的匿名Sequence物件進行動態擴充套件。所謂裝飾設計模式就是在不繼承的情況下,使類變得更強大(例如Java的I/O流)。最後在末端操作中呼叫Sequence的迭代器進行迭代,觸發中間操作,並獲取其返回值進行處理並輸出。
參考資料:
- 《Kotlin實戰》
- Kotlin官網