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

前序

??????之前探究集合的函數(shù)式Api時發(fā)現(xiàn)秦躯,這些函數(shù)都會遍歷集合并且提早創(chuàng)建新集合,每一步的中間結(jié)果會被存儲在新集合中斩披。當(dāng)數(shù)據(jù)量很大時端姚,調(diào)用十分的低效晕粪。

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

序列

??????序列對每個元素逐個執(zhí)行所有處理步驟,可以避免構(gòu)建中間變量渐裸,提高整個集合處理鏈的性能巫湘。序列也稱為惰性集合,序列與Java8中的Stream很像昏鹃,序列是Kotlin對流這種概念提供的實現(xiàn)尚氛。

??????Kotlin惰性集合操作的入口是 Sequence 接口。該接口只有 iterator 方法洞渤,用來從序列中獲取值阅嘶。

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

創(chuàng)建序列

創(chuàng)建序列有四種方式:

  • 1、使用頂層函數(shù)sequenceOf()载迄,將元素作為其參數(shù)讯柔。(類似創(chuàng)建集合的那一堆頂層函數(shù),如listOf)
val numbers= sequenceOf(1,2,3,4,5,6,7,8,9,10)
  • 2护昧、使用Iterable的擴(kuò)展函數(shù)asSequence()將集合轉(zhuǎn)換為序列魂迄。(常用)
val numbers = (1..10).toList().asSequence()
  • 3、使用generateSequence()惋耙。給定一個初識的元素捣炬,并提供函數(shù)計算下一個元素。該函數(shù)會一直生成序列的元素绽榛,直到函數(shù)實參返回null為止湿酸。如果函數(shù)實參不返回null,則該序列將是一個無限序列:
val numbers = generateSequence(6){
    it + 2
}

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

val numbers = generateSequence(6){
    if (it < 10) 
        it + 2 
    else 
        null
}
  • 4、使用sequence()函數(shù).該函數(shù)接收一個函數(shù)類型為 SequenceScope<T>.() -> Unit的實參灭美⊥评#可以在傳遞給sequence()函數(shù)的lambda表達(dá)式中使用SequenceScope對象的 yield() 和 yieldAll() 添加序列元素。yield()用于添加單個序列元素; yieldAll()用于將列表或序列中的元素轉(zhuǎn)化為新序列的元素冲粤。
val numbers = sequence{
    yield(1)
    yieldAll(listOf(2,3))
    yieldAll(setOf(4,5))
    yieldAll(generateSequence(6){
        if (it < 10)
            it + 1
        else
            null
    })
}

中間操作和終端操作

??????序列一樣可以像集合一樣調(diào)用函數(shù)式Api美莫,但序列的操作分為兩大類:中間操作和終端操作页眯。

??????中間操作的定義:中間操作始終是惰性的梯捕,中間操作返回的是另一個序列。

可以通過函數(shù)的返回信息窝撵,判斷是否為中間操作:

//filter函數(shù)傀顾,返回Sequence<T>,中間操作碌奉。
//注意這是一個帶Sequence<T>接收者的函數(shù)類型參數(shù)6淘寒砖!
public fun <T> Sequence<T>.filter(predicate: (T) -> Boolean): Sequence<T> {
    return FilteringSequence(this, true, predicate)
}

//map函數(shù),返回Sequence<T>嫉拐,中間操作
//注意這是一個帶Sequence<T>接收者的函數(shù)類型參數(shù)Aǘ肌!
public fun <T, R> Sequence<T>.map(transform: (T) -> R): Sequence<R> {
    return TransformingSequence(this, transform)
}

惰性怎么理解呢婉徘?執(zhí)行以下例子:

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

??????結(jié)果是并無任何打印漠嵌,表示filter和map函數(shù)被"延遲"了,只有配合末端操作求結(jié)果時盖呼,中間操作的才被觸發(fā)儒鹿。

??????末端操作定義:觸發(fā)執(zhí)行所有的延期計算(指中間操作),并返回一個結(jié)果几晤,結(jié)果可能是集合约炎、數(shù)字等。

//forEach函數(shù)蟹瘾,返回值不是序列圾浅,末端操作
//注意這是一個帶Sequence<T>接收者的函數(shù)類型參數(shù)!
public inline fun <T> Sequence<T>.forEach(action: (T) -> Unit): Unit {
    for (element in this) action(element)
}

//count函數(shù)热芹,返回值不是序列贱傀,末端操作
//注意這是一個帶Sequence<T>接收者的函數(shù)類型參數(shù)!
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
}

中間操作為什么是惰性的

??????估計很多小伙伴應(yīng)該和我一樣伊脓,很好奇為什么中間操作是惰性的府寒?想要得到答案,那就只能去查看源碼進(jìn)行分析了报腔,先看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()函數(shù)會創(chuàng)建一個匿名的Sequence匿名類對象株搔,并將集合的迭代器存儲起來,作為自己iterator()方法的返回值纯蛾。

中間操作

(可以直接跳過代碼纤房,看結(jié)果)

#filter函數(shù)
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
        
        //計算該中間操作的實現(xiàn)(簡單說就是在)
        private fun calcNext() {
            while (iterator.hasNext()) {
                val item = iterator.next()
                //執(zhí)行謂詞lambda,判斷是否符合條件
                if (predicate(item) == sendWhen) {
                    //符合條件則獲取元素
                    nextItem = item
                    //并修改狀態(tài)
                    nextState = 1
                    return
                }
            }
            nextState = 0
        }

        override fun next(): T {
            //檢查機(jī)制
            if (nextState == -1)
                calcNext()
            if (nextState == 0)
                throw NoSuchElementException()
            //獲取值翻诉,并將狀態(tài)重置
            val result = nextItem
            nextItem = null
            nextState = -1
            @Suppress("UNCHECKED_CAST")
            //返回值
            return result as T
        }

        override fun hasNext(): Boolean {
            //在上一個序列的迭代器的基礎(chǔ)上炮姨,進(jìn)行謂詞運算,判斷是否有下一個
            if (nextState == -1)
                calcNext()
            return nextState == 1
        }
    }
}
#map函數(shù)
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 {
            //用函數(shù)類型參數(shù)進(jìn)行運算碰煌,返回值
            return transformer(iterator.next())
        }

        override fun hasNext(): Boolean {
            //沿用上一個序列的迭代器的hasNext()函數(shù)
            return iterator.hasNext()
        }
    }

    internal fun <E> flatten(iterator: (R) -> Iterator<E>): Sequence<E> {
        return FlatteningSequence<T, R, E>(sequence, transformer, iterator)
    }
}

結(jié)合其他中間操作的代碼得到的結(jié)果是:

  • 1舒岸、中間操作都會獲取上一個序列(因為是帶序列接收者的lambda)的迭代器。
  • 2芦圾、自身實現(xiàn)Sequence接口所獲得的iterator()函數(shù)蛾派,將返回一個匿名的迭代器對象。
  • 3、自身的迭代器對象的hasNext()函數(shù)將調(diào)用上一個序列的迭代器的hasNext()函數(shù)洪乍,或在上一個序列的迭代器的基礎(chǔ)上進(jìn)行封裝眯杏。
  • 4、自身的迭代器對象的next()函數(shù)壳澳,將調(diào)用該中間操作所接收的函數(shù)類型參數(shù)進(jìn)行運算岂贩,最后返回一個值。
  • 總的來說巷波,中間操作只是對迭代器的一層層封裝河闰,而內(nèi)部并無使用while或for進(jìn)行迭代。

末端操作

(可以直接跳過代碼褥紫,看結(jié)果)

#forEach函數(shù)
public inline fun <T> Sequence<T>.forEach(action: (T) -> Unit): Unit {
    //迭代進(jìn)行(注意該this姜性,是指最后一個中端操作返回的Sequence對象)
    for (element in this) 
        action(element)
}
#count函數(shù)
public inline fun <T> Sequence<T>.count(predicate: (T) -> Boolean): Int {
    var count = 0
    //迭代進(jìn)行(注意該this,是指最后一個中端操作返回的Sequence對象)
    for (element in this) 
        if (predicate(element)) 
            checkCountOverflow(++count)
    return count
}

結(jié)合其他末端操作的代碼得到的結(jié)果是:

  • 1髓考、末端操作使用中間操作返回Sequence對象(因為末端操作也是帶序列接收者的lambda)獲取迭代器部念,用于forEach循環(huán)中。(這就解析了為什么要加末端操作才能是中間操作被執(zhí)行氨菇,因為只有在forEach中迭代器才能被使用儡炼。這時,中間操作的返回值才能從迭代器的next()中返回查蓉。)
  • 2乌询、在forEach中對每一個元素作為值傳給函數(shù)類型的參數(shù)進(jìn)行運算。

整體流程如下所示:

image

如果在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實例嵌套創(chuàng)建,得到最后一個中間操作的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

??????可以看到鹃共,各個中間操作都會產(chǎn)生的Sequence對象鬼佣,都按照其調(diào)用的順序進(jìn)行嵌套,最后得到最后一個中間操作的Sequence對象霜浴。

提取重點代碼(2):

//獲取最后一個中間操作的Sequence對象
Iterator var4 = $this$forEach$iv.iterator();

//末端操作迭代迭代器晶衷,調(diào)用迭代器的next()方法時,將按照中間操作嵌套的瞬間執(zhí)行中間操作對應(yīng)的迭代器next方法阴孟,得到中間操作的返回值
//晌纫。最后一個中間操作的返回值交由末端操作處理
while(var4.hasNext()) {
 Object element$iv = var4.next();
 //..
}

??????末端操作的for循環(huán)會變成while循環(huán),但還是依據(jù)迭代器進(jìn)行迭代永丝。迭代過程中不斷調(diào)用各個中間操作的迭代器锹漱,執(zhí)行中間操作,最后將中間操作得到的值交由末端操作進(jìn)行處理类溢。

總結(jié)

??????Kotlin的序列使用裝飾設(shè)計模式凌蔬,對集合轉(zhuǎn)換的匿名Sequence對象進(jìn)行動態(tài)擴(kuò)展。所謂裝飾設(shè)計模式就是在不繼承的情況下闯冷,使類變得更強(qiáng)大(例如Java的I/O流)砂心。最后在末端操作中調(diào)用Sequence的迭代器進(jìn)行迭代,觸發(fā)中間操作蛇耀,并獲取其返回值進(jìn)行處理并輸出辩诞。

image

參考資料:

android Kotlin系列:

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

Kotlin知識歸納(二) —— 讓函數(shù)更好調(diào)用

Kotlin知識歸納(三) —— 頂層成員與擴(kuò)展

Kotlin知識歸納(四) —— 接口和類

Kotlin知識歸納(五) —— Lambda

Kotlin知識歸納(六) —— 類型系統(tǒng)

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

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

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

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

Kotlin知識歸納(十一) —— 高階函數(shù)

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

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

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

image
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市纺涤,隨后出現(xiàn)的幾起案子译暂,更是在濱河造成了極大的恐慌,老刑警劉巖撩炊,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件外永,死亡現(xiàn)場離奇詭異,居然都是意外死亡拧咳,警方通過查閱死者的電腦和手機(jī)伯顶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來骆膝,“玉大人祭衩,你說我怎么就攤上這事≡那” “怎么了掐暮?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長政钟。 經(jīng)常有香客問我路克,道長,這世上最難降的妖魔是什么养交? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任衷戈,我火速辦了婚禮,結(jié)果婚禮上层坠,老公的妹妹穿的比我還像新娘殖妇。我一直安慰自己,他們只是感情好破花,可當(dāng)我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布谦趣。 她就那樣靜靜地躺著,像睡著了一般座每。 火紅的嫁衣襯著肌膚如雪前鹅。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天峭梳,我揣著相機(jī)與錄音舰绘,去河邊找鬼蹂喻。 笑死,一個胖子當(dāng)著我的面吹牛捂寿,可吹牛的內(nèi)容都是我干的口四。 我是一名探鬼主播,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼秦陋,長吁一口氣:“原來是場噩夢啊……” “哼蔓彩!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起驳概,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤赤嚼,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后顺又,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體更卒,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年稚照,在試婚紗的時候發(fā)現(xiàn)自己被綠了逞壁。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡锐锣,死狀恐怖腌闯,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情雕憔,我是刑警寧澤姿骏,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站斤彼,受9級特大地震影響分瘦,放射性物質(zhì)發(fā)生泄漏辑奈。R本人自食惡果不足惜丑罪,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一耸峭、第九天 我趴在偏房一處隱蔽的房頂上張望茉稠。 院中可真熱鬧,春花似錦钾麸、人聲如沸寞秃。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽土陪。三九已至,卻和暖如春肴熏,著一層夾襖步出監(jiān)牢的瞬間鬼雀,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工蛙吏, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留源哩,地道東北人鞋吉。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像励烦,于是被迫代替她去往敵國和親谓着。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,792評論 2 345