Kotlin知識概括(八) —— 序列

前序

      以前探究集合的函數式Api時發現,這些函數都會遍歷集合而且提前建立新集合,每一步的中間結果會被存儲在新集合中。當數據量很大時,調用十分的低效。android

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 方法,用來從序列中獲取值。bash

public interface Sequence<out T> {
    public operator fun iterator(): Iterator<T>
}
複製代碼

建立序列

建立序列有四種方式:ide

  • 一、使用頂層函數sequenceOf(),將元素做爲其參數。(相似建立集合的那一堆頂層函數,如listOf)
val numbers= sequenceOf(1,2,3,4,5,6,7,8,9,10)
複製代碼
  • 二、使用Iterable的擴展函數asSequence()將集合轉換爲序列。(經常使用)
val numbers = (1..10).toList().asSequence()
複製代碼
  • 三、使用generateSequence()。給定一個初識的元素,並提供函數計算下一個元素。該函數會一直生成序列的元素,直到函數實參返回null爲止。若是函數實參不返回null,則該序列將是一個無限序列:
val numbers = generateSequence(6){
    it + 2
}
複製代碼

使用generateSequence()提供有限序列:函數

val numbers = generateSequence(6){
    if (it < 10) 
        it + 2 
    else 
        null
}
複製代碼
  • 四、使用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,但序列的操做分爲兩大類:中間操做和終端操做。post

      中間操做的定義:中間操做始終是惰性的,中間操做返回的是另外一個序列。性能

能夠經過函數的返回信息,判斷是否爲中間操做:ui

//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)
}
複製代碼

惰性怎麼理解呢?執行如下例子:this

val list = (1..10).toList()
list.asSequence()
    .filter {
        println("filter $it")
        it % 2 == 0
    }.map {
        println("map $it")
        it * it
    }
複製代碼

      結果是並沒有任何打印,表示filter和map函數被"延遲"了,只有配合末端操做求結果時,中間操做的才被觸發。

      末端操做定義:觸發執行全部的延期計算(指中間操做),並返回一個結果,結果多是集合、數字等。spa

//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)
    }
}
複製代碼

結合其餘中間操做的代碼獲得的結果是:

  • 一、中間操做都會獲取上一個序列(由於是帶序列接收者的lambda)的迭代器。
  • 二、自身實現Sequence接口所得到的iterator()函數,將返回一個匿名的迭代器對象。
  • 三、自身的迭代器對象的hasNext()函數將調用上一個序列的迭代器的hasNext()函數,或在上一個序列的迭代器的基礎上進行封裝。
  • 四、自身的迭代器對象的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
}
複製代碼

結合其餘末端操做的代碼獲得的結果是:

  • 一、末端操做使用中間操做返回Sequence對象(由於末端操做也是帶序列接收者的lambda)獲取迭代器,用於forEach循環中。(這就解析了爲何要加末端操做才能是中間操做被執行,由於只有在forEach中迭代器才能被使用。這時,中間操做的返回值才能從迭代器的next()中返回。)
  • 二、在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的迭代器進行迭代,觸發中間操做,並獲取其返回值進行處理並輸出。

參考資料:

android Kotlin系列:

Kotlin知識概括(一) —— 基礎語法

Kotlin知識概括(二) —— 讓函數更好調用

Kotlin知識概括(三) —— 頂層成員與擴展

Kotlin知識概括(四) —— 接口和類

Kotlin知識概括(五) —— Lambda

Kotlin知識概括(六) —— 類型系統

Kotlin知識概括(七) —— 集合

Kotlin知識概括(八) —— 序列

Kotlin知識概括(九) —— 約定

Kotlin知識概括(十) —— 委託

Kotlin知識概括(十一) —— 高階函數

Kotlin知識概括(十二) —— 泛型

Kotlin知識概括(十三) —— 註解

Kotlin知識概括(十四) —— 反射

相關文章
相關標籤/搜索