Kotlin知識歸納(八) —— 序列

大棋發表於2019-06-29

前序

      之前探究集合的函式式Api時發現,這些函式都會遍歷集合並且提早建立新集合,每一步的中間結果會被儲存在新集合中。當資料量很大時,呼叫十分的低效。

fun main(args: Array<String>) {
    val list = (1..10).toList()
    list.filter { 
        it % 2 == 0
    }.map { 
        it * it
    }.forEach {
        println(it)
    }
}
複製程式碼

Kotlin知識歸納(八) —— 序列

序列

      序列對每個元素逐個執行所有處理步驟,可以避免構建中間變數,提高整個集合處理鏈的效能。序列也稱為惰性集合,序列與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
    }
複製程式碼

Kotlin知識歸納(八) —— 序列
      結果是並無任何列印,表示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中對每一個元素作為值傳給函式型別的引數進行運算。

整體流程如下所示:

Kotlin知識歸納(八) —— 序列

如果在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知識歸納(八) —— 序列

參考資料:

android Kotlin系列:

Kotlin知識歸納(一) —— 基礎語法

Kotlin知識歸納(二) —— 讓函式更好呼叫

Kotlin知識歸納(三) —— 頂層成員與擴充套件

Kotlin知識歸納(四) —— 介面和類

Kotlin知識歸納(五) —— Lambda

Kotlin知識歸納(六) —— 型別系統

Kotlin知識歸納(七) —— 集合

Kotlin知識歸納(八) —— 序列

Kotlin知識歸納(九) —— 約定

Kotlin知識歸納(十) —— 委託

Kotlin知識歸納(十一) —— 高階函式

Kotlin知識歸納(十二) —— 泛型

Kotlin知識歸納(十三) —— 註解

Kotlin知識歸納(十四) —— 反射

Kotlin知識歸納(八) —— 序列

相關文章