淺談Kotlin中集合和函數式API完全解析-上篇(八)

簡述: 今天帶來的是Kotlin淺談系列的第八講,這講我們一起來聊聊Kotlin這門語言對函數式編程的支持讥蔽。我們都知道在kotlin這門語言中函數榮升成為了一等公民涣易,所以在支持函數式編程的方面,Kotlin這門語言也是非常給力的冶伞,并且在Kotlin中語法也盡量推薦接近函數式編程的風格都毒。學過以及了解過函數式編程的小伙伴都知道函數式編程最吸引人的地方,莫過于它擁有豐富的函數式操作符碰缔,可以使用一種全新的編程方式去操作集合數據账劲。其中操作符最流行莫過于函數式中“三板斧”(過濾filter、映射map金抡、折疊foldLeft/化約reduce)瀑焦。那么小伙伴會提問了:

  • 1、那Kotlin語言中有這些操作符嗎梗肝?

答: 當然有榛瓮,不僅有這些而且還有很多很豐富的函數式操作符,僅從這方面來說Kotlin這門語言是函數式編程語言一點也不為過巫击。

  • 2禀晓、那今天是講函數式API嗎?

答: 沒錯坝锰,今天會對Kotlin中所有函數式操作符API做詳細的講解粹懒,包括基本使用、基本定義顷级、實質原理三個方面來做介紹凫乖,力求做到完全解析

  • 3、有什么小建議?

最后帽芽,一個小建議删掀,由于Kotlin中的函數式API有很多,有些也不是經常使用的导街,建議先將經常使用的操作符(我會做好標記)理解披泪、掌握。其他不常用的后續(xù)可以返回來查找即可

今天闡述的內容點很簡單搬瑰,主要有以下三點:

  • 1付呕、Kotlin中集合的詳細介紹和完全解析
  • 2、Kotlin中函數式API操作符的分類
  • 3跌捆、Kotlin中函數式API操作符的詳解

一徽职、Kotlin中集合的詳細介紹和完全解析

在介紹函數式API操作符之前,有必要去了解一下這些操作符操作的對象集合佩厚。實際上姆钉,Kotlin語言中的集合和Java還是有一定區(qū)別的。在Kotlin中集合主要分為了兩個大類抄瓦,一類是可變集合(具有訪問和修改權限)潮瓶,另一類是只讀集合(只具有訪問權限)(注意: 這里不能說不可變集合,只能說是具有可讀權限钙姊,關于這個不可變和可讀討論之前博客有相關闡述)毯辅。Kotlin的集合設計與Java集合設計有一個很重要區(qū)別就是Kotlin把集合訪問接口和集合修改接口分開了。

  • 1煞额、Kotlin為什么把集合設計為可變和只讀兩種思恐?

關于這個問題,實際上之前的var和val的分離設計已經回答了一部分膊毁。Kotlin這門考慮到實際開發(fā)中方便和程序中數據發(fā)生的事情更容易讓人理解胀莹,所以才有此設計。我們設想一下這樣的場景婚温,kotlin中定義一個函數描焰,函數的參數是一個可變集合,以kotlin開發(fā)規(guī)則而言栅螟,傳遞一個可變集合作為參數荆秦,實際上也在表明在該函數體內部涉及到修改集合操作。如果傳遞的是一個只讀集合作為參數力图,那么表明在該函數體內是不會涉及到修改集合操作步绸,只允許訪問集合√履模看到如此的設計你是否已經愛上了這門語言靡努,也就是這門語言在各個方面和開發(fā)細節(jié)上都是花了很多功夫的,力求做到任何一步都是讓開發(fā)者開發(fā)更簡單和更容易理解晓折。

  • 2惑朦、集合的分類

在kotlin.collections包中包含相應集合。主要包含Iterable(只讀迭代器)和MutableIterable(可變迭代器)漓概、Collection和MutableCollection漾月、List和MutableList、Set和MutableSet胃珍、Map和MutableMap

<img src="https://user-gold-cdn.xitu.io/2018/5/7/1633b326c82d8938?w=459&h=219&f=jpeg&s=60695"/>

  • 3梁肿、可變集合與只讀集合之間的區(qū)別和聯(lián)系(以Collection集合為例)

Collection只讀集合與MutableCollectio可變集合區(qū)別:

在Collection只具有訪問元素的方法,不具有類似add觅彰、remove吩蔑、clear之類的方法,而在MutableCollection中則相比Collection多出了修改元素的方法填抬。

Collection只讀集合與MutableCollectio可變集合聯(lián)系:

MutableCollection實際上是Collection集合接口的子接口烛芬,他們之間是繼承關系。

image
  • 4飒责、集合之間類的關系

通過Collection.kt文件中可以了解到有這些集合Iterable(只讀迭代器)和MutableIterable(可變迭代器)赘娄、Collection和MutableCollection、List和MutableList宏蛉、Set和MutableSet遣臼、Map和MutableMap。那么它們之間的類關系圖是怎樣的拾并。

Iterable和MutableIterable接口分別是只讀和可變集合的父接口揍堰,Collection繼承Iterable然后List、Set接口繼承自Collection嗅义,Map接口比較特殊它是單獨的接口个榕,然后MutableMap接口是繼承自Map.

image
  • 5、Java中的集合與Kotlin中集合對應關系

我們剛剛說到在Kotlin中集合的設計與Java不一樣芥喇,但是每一個Kotlin的接口都是其對應的Java集合接口的一個實例西采,也就是在Kotlin中集合與Kotlin中的集合存在一定的對應關系。Java中的ArrayList類和HashSet類實際上Kotlin中的MutableList和MutableSet集合接口的實現(xiàn)類继控。把這種關系加上械馆,上面的類關系圖可以進一步完善。

image
  • 6武通、集合的初始化

由于在Kotlin中集合主要分為了只讀集合和可變集合霹崎,那么初始化只讀集合和可變集合的函數也不一樣。以List集合為例冶忱,對于只讀集合初始化一般采用listOf()方法尾菇,對于可變集合初始化一般采用mutableListOf()或者直接創(chuàng)建ArrayList<E>,因為mutableListOf()內部實現(xiàn)也是也還是采用創(chuàng)建ArrayList,這個ArrayList實際上是Java中的java.util.ArrayList<E>,只不過在Kotlin中使用typealias(關于typealias的使用之前博客有過詳細介紹)取了別名而已派诬。關于具體內容請參考這個類kotlin.collections.TypeAliasesKt實現(xiàn)

  • 7劳淆、集合使用的注意事項

注意點一: 在代碼的任何地方都優(yōu)先使用只讀集合,只在需要修改集合的情況下才去使用可變集合

注意點二: 只讀集合不一定是不可變的默赂,關于這個只讀和不可變類似于val的只讀和不可變原理沛鸵。

注意點三: 不能把一個只讀類型的集合作為參數傳遞給一個帶可變類型集合的函數。

二缆八、Kotlin中函數式API操作符的分類

Kotlin中函數式API操作符有很多曲掰,函數式中“三板斧”必須有的,定義和用法也是不盡相同奈辰。與其雜亂的死記硬背栏妖,不如先從大體上給這些API操作符分類,然后針對每一類去分析奖恰、理解底哥、掌握,分類的規(guī)則也是按照各個操作符的功能來分房官。Kotlin中函數式API操作符主要有以下幾大類趾徽。

  • 1、篩選類操作符(Filtering operations):主要有以下操作符

slice

filter系列

image

drop系列

image

take系列

image
  • 2翰守、并集類操作符(Aggregate operations):主要有以下操作符

any孵奶、all、count蜡峰、none

fold系列

image

forEach系列

image

max系列

image

min系列

image

reduce系列

image

sum系列

image
  • 3了袁、映射類操作符(Mapping operations):主要有以下操作符

flatMap系列

image

groupBy系列

image

map系列

image
  • 4、元素類操作符(Element operations):主要有以下操作符

elementAt系列

image

first系列

image

find系列

image

indexOf系列

image

last系列

image

single系列

image
  • 5湿颅、排序類操作符(Ordering operations):主要有以下操作符

reverse

sort系列

image
  • 6载绿、生成類操作符(Generation operations):主要有以下操作符

partition

plus系列

image

zip系列

image

三、篩選類函數式API的詳解(Filtering operations)

slice操作符

  • 1油航、基本定義

slice操作符顧名思義是"切片"的意思崭庸,也就是它可以取集合中一部分元素或者某個元素,最后也是組合成一個新的集合谊囚。它有兩個重載函數怕享,一個傳入IntRange對象指定切片起始位置和終止位置,最后切出的是一個范圍的元素加入到新集合中镰踏。另一個是傳入一個Iterable下標集合函筋,也就會從指定下標分別切出對應的元素,最后放入到新集合中奠伪。

  • 2跌帐、源碼定義
public fun <T> List<T>.slice(indices: IntRange): List<T> {
    if (indices.isEmpty()) return listOf()
    return this.subList(indices.start, indices.endInclusive + 1).toList()
}

public fun <T> List<T>.slice(indices: Iterable<Int>): List<T> {
    val size = indices.collectionSizeOrDefault(10)
    if (size == 0) return emptyList()
    val list = ArrayList<T>(size)
    for (index in indices) {
        list.add(get(index))
    }
    return list
}
  • 3首懈、源碼解析

首先,slice函數是List<T>的一個擴展函數谨敛,它有兩個重載函數究履,一個是接收IntRange對象,另一個是接收元素下標的集合對象佣盒,最終函數是返回一個List<T>集合挎袜。接收IntRange對象的函數實現(xiàn)很簡單顽聂,主要是通過IntRange對象拿到對應的start,end位置肥惭,然后利用subList拿到子集合,最后返回這個子集合紊搪。接收元素下標的集合的函數蜜葱,是內部創(chuàng)建一個新的集合對象,然后遍歷整個原集合把元素下標集合中的元素加入到新創(chuàng)建的集合中耀石,最后返回這個新的集合對象牵囤。

  • 4、原理圖解
image
  • 5滞伟、使用場景

slice by IntRange一般使用場景: 用于切取一段下標范圍的子集合

slice by itertar index一般使用場景: 用于切取某個或者某些下標元素組成的集合

fun main(args: Array<String>) {
    val numberList = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9)

    val newNumberList1 = numberList.slice(IntRange(3, 6))
    print("slice by IntRange: ")
    newNumberList1.forEach {
        print("$it ")
    }

    println()

    val newNumberList2 = numberList.slice(listOf(1, 3, 7))
    print("slice by iterator index: ")
    newNumberList2.forEach {
        print("$it ")
    }
}

<img src="https://user-gold-cdn.xitu.io/2018/5/10/16347d4f55554ea1?w=377&h=132&f=png&s=25552"/>

filter和filterTo操作符

  • 1揭鳞、基本定義:

根據用戶定義的條件篩選集合中的數據,并且由此產生一個新的集合梆奈。這個新的集合是原集合的子集野崇。

  • 2、源碼定義:
public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {
    return filterTo(ArrayList<T>(), predicate)
}

public inline fun <T, C : MutableCollection<in T>> Iterable<T>.filterTo(destination: C, predicate: (T) -> Boolean): C {
    for (element in this) if (predicate(element)) destination.add(element)
    return destination
}
  • 3亩钟、源碼解析:

首先乓梨,從整體上可以看出filter是一個Iterable<T>的擴展函數并且是一個內聯(lián)函數,該函數接收一個以接收T類型返回一個Boolean類型的lambda表達式predicate作為參數清酥,所以它還是一個高階函數扶镀,返回一個List<T>集合

然后,看具體的內部實現(xiàn)是調用了另一個函數filterTo焰轻,并傳入新創(chuàng)建的ArrayList<T>()可變集合對象臭觉,然后繼續(xù)把lambda表達式作為參數傳遞到filterTo函數中,在filterTo函數去實現(xiàn)真正的過濾操作辱志。傳入的lambda表達式predicate實際上就是外部調用者傳入的過濾條件胧谈,可以看到在filterTo內部是利用一個for循環(huán)進行篩選判斷符合lambda表達式條件的,就添加到filter調用filterTo函數傳遞的參數ArrayList<T>新集合對象中荸频,最后就是返回這個ArrayList<T>新集合對象菱肖。所以filter最后篩選出來的還是一個集合。

  • 4旭从、原理圖解:
image
  • 5稳强、使用場景:

filter的操作符使用場景: 從一個集合篩選出符合條件的元素场仲,并以一個新集合返回。

fun main(args: Array<String>) {
    val numberList = listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
    val newNumberList = numberList.filter { number ->
        number % 2 == 0//篩選出偶數
    }

    newNumberList.forEach { print("$it   ")}
}

<img src="https://user-gold-cdn.xitu.io/2018/5/9/16340991b8450c64?w=339&h=101&f=jpeg&s=52193"/>

filterTo的操作符使用場景: 從多個集合篩選出符合條件的元素退疫,并最終用一個集合進行收集從每個集合篩選出的元素渠缕。

fun main(args: Array<String>) {
    val numberList1 = listOf(23, 65, 14, 57, 99, 123, 26, 15, 88, 37, 56)
    val numberList2 = listOf(13, 55, 24, 67, 93, 137, 216, 115, 828, 317, 16)
    val numberList3 = listOf(20, 45, 19, 7, 9, 3, 26, 5, 38, 75, 46)
    
    //需要注意一點的是,我們從源碼看到filterTo第一個參數destination是一個可變集合類型褒繁,所以這里使用的mutableListOf初始化
    val newNumberList = mutableListOf<Int>().apply {
        numberList1.filterTo(this) {
            it % 2 == 0
        }
        numberList2.filterTo(this) {
            it % 2 == 0
        }
        numberList3.filterTo(this) {
            it % 2 == 0
        }
    }

    print("從三個集合篩選出的偶數集合: ")
    newNumberList.forEach {
        print("$it   ")
    }
}

<img src="https://user-gold-cdn.xitu.io/2018/5/10/16347ccc2ab884e4?w=675&h=108&f=png&s=31156"/>

filterIndexed和filterIndexedTo操作符

  • 1亦鳞、基本定義:

filterIndexed操作符定義和filter幾乎是一樣的。他們之前唯一的區(qū)別是filterIndexed篩選條件的lambda表達式多暴露一個參數那就是元素在集合中的index.也就是外部可以拿到這個元素以及這個元素的index. 特別適合需要集合元素index參與篩選條件的case棒坏。

  • 2燕差、源碼定義:
public inline fun <T> Iterable<T>.filterIndexed(predicate: (index: Int, T) -> Boolean): List<T> {
    return filterIndexedTo(ArrayList<T>(), predicate)
}

public inline fun <T, C : MutableCollection<in T>> Iterable<T>.filterIndexedTo(destination: C, predicate: (index: Int, T) -> Boolean): C {
    forEachIndexed { index, element ->
        if (predicate(index, element)) destination.add(element)
    }
    return destination
}

public inline fun <T> Iterable<T>.forEachIndexed(action: (index: Int, T) -> Unit): Unit {
    var index = 0
    for (item in this) action(index++, item)
}

  • 3、源碼解析:

首先坝冕,要了解filterIndexed實現(xiàn)原理還需要涉及兩個操作符: filterIndexedTo徒探、forEachIndexed。從整體上可以看出filterIndexed是一個Iterable<T>的擴展函數并且是一個內聯(lián)函數喂窟,該函數接收一個以接收Int類型和接收T類型兩個參數返回一個Boolean類型的lambda表達式predicate作為參數测暗,所以它還是一個高階函數,返回一個List<T>集合磨澡。

然后碗啄,大部分實現(xiàn)的原理和filter類似,filterIndexedTo和filterIndexed類似稳摄,唯一可以說下的就是index稚字,index實際上是forEachIndexed內部的一個迭代自增計數器,可以在內部每次迭代秩命,就會計數器就會自增一次,并且把這個index回調到外部尉共。

  • 4、使用場景:
fun main(args: Array<String>) {
    val numberList = listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

    val newNumberList = numberList.filterIndexed { index, number ->
        index < 5 && number % 2 == 0 //篩選出集合中前五個元素中是偶數的數
    }

    newNumberList.forEach {
        print("$it  ")
    }
}

<img src="https://user-gold-cdn.xitu.io/2018/5/9/16343789dbecbb0f?w=333&h=119&f=jpeg&s=49024"/>

filterIsInstance和filterIsInstanceTo操作符

  • 1弃锐、基本定義

filterIsInstance操作符是filter操作符一個特定應用袄友,從集合中篩選出instance某個特定類型元素并把該元素強轉成該類型,最后返回這些元素集合霹菊。

  • 2剧蚣、源碼定義
public inline fun <reified R> Iterable<*>.filterIsInstance(): List<@kotlin.internal.NoInfer R> {
    return filterIsInstanceTo(ArrayList<R>())
}

public inline fun <reified R, C : MutableCollection<in R>> Iterable<*>.filterIsInstanceTo(destination: C): C {
    for (element in this) if (element is R) destination.add(element)
    return destination
}
  • 3、源碼解析

首先旋廷,filterIsInstance是一個擴展函數鸠按,它的主要實現(xiàn)是借助于filterIsInstanceTo,通過外部傳入的R泛型饶碘,創(chuàng)建一個R泛型的ArrayList可變集合目尖,用于收集原集合中instance R類型的元素.可以看出在filterIsInstanceTo內部是遍歷集合然后利用is判斷屬于R類型的元素就加入到集合中,最后返回該集合扎运。

  • 4瑟曲、使用場景

filterInstance使用場景: 適用于一個抽象類集合中還有多種子類型的元素饮戳,可以很方便篩選對應子類型的元素,并組成一個集合返回洞拨。

filterInstanceTo使用場景:
基本作用和filterInstance一致扯罐,不過唯一的區(qū)別就是這個可變集合ArrayList<R>不是在內部創(chuàng)建,而是由外部創(chuàng)建烦衣,非常適合篩選多個集合的情況歹河。

下面看個例子,我們來看下不使用filterInstance和使用filterInstance情況對比花吟。

沒有使用filterInstance秸歧,而是使用filter和map集合相結合。(當你不知道有filterInstance操作符示辈,估計很多都是這種實現(xiàn)的)

abstract class Animal(var name: String, var age: Int){
    abstract fun eatFood(): String
}
class Bird(name: String, age: Int): Animal(name, age){
    override fun eatFood() = "bird eat worm"
}
class Cat(name: String, age: Int) : Animal(name, age) {
    override fun eatFood() = "Cat eat Fish"
}
class Dog(name: String, age: Int) : Animal(name, age) {
    override fun eatFood() = "dog eat bone"
}

fun main(args: Array<String>) {
    val animalList: List<Animal> = listOf(Bird(name = "Bird1", age = 12),
            Cat(name = "Cat1", age = 18),
            Cat(name = "Cat3", age = 20),
            Dog(name = "Dog2", age = 8),
            Cat(name = "Cat2", age = 8),
            Bird(name = "Bird2", age = 14),
            Bird(name = "Bird3", age = 16),
            Dog(name = "Dog1", age = 18)
    )

    //篩選出個所有Dog的信息寥茫,借助filter和map操作符
    animalList.filter {
        it is Dog
    }.map {
        it as Dog
    }.forEach {
        println("${it.name} is ${it.age} years old, and ${it.eatFood()}")
    }
}

使用filterInstance操作符實現(xiàn)

fun main(args: Array<String>) {
    val animalList: List<Animal> = listOf(Bird(name = "Bird1", age = 12),
            Cat(name = "Cat1", age = 18),
            Cat(name = "Cat3", age = 20),
            Dog(name = "Dog2", age = 8),
            Cat(name = "Cat2", age = 8),
            Bird(name = "Bird2", age = 14),
            Bird(name = "Bird3", age = 16),
            Dog(name = "Dog1", age = 18)
    )

    //篩選出個所有Dog的信息遣蚀,借助filterIsInstance操作符
    animalList.filterIsInstance<Dog>().forEach { println("${it.name} is ${it.age} years old, and ${it.eatFood()}") }
}

<img src="https://user-gold-cdn.xitu.io/2018/5/10/16348961e7554dae?w=352&h=147&f=png&s=26288"/>

filterNot和filterNotTo操作符

  • 1矾麻、基本定義

從一個集合篩選出符合條件之外的元素,并以一個新集合返回芭梯,它是filter操作符取反操作险耀。

  • 2、源碼定義
public inline fun <T> Iterable<T>.filterNot(predicate: (T) -> Boolean): List<T> {
    return filterNotTo(ArrayList<T>(), predicate)
}
public inline fun <T, C : MutableCollection<in T>> Iterable<T>.filterNotTo(destination: C, predicate: (T) -> Boolean): C {
    for (element in this) if (!predicate(element)) destination.add(element)
    return destination
}
  • 3玖喘、源碼解析

實際上filterNot沒什么可說的甩牺,它也是借助于filterNotTo操作具體,和filterTo唯一區(qū)別就是判斷條件取反

  • 4累奈、原理圖解
image
  • 5贬派、使用場景

使用場景就是filter使用的取反條件使用,當然你也可以繼續(xù)使用filter操作符澎媒,并且篩選條件為取反條件搞乏。

filterNotNull和filterNotNullTo操作符

  • 1、基本定義

filterNotNull操作符可以過濾集合中為null的元素,那么同理filterNotNullTo才是真正過濾操作戒努,但是需要從外部傳入一個可變集合请敦。

  • 2、源碼定義
public fun <T : Any> Iterable<T?>.filterNotNull(): List<T> {
    return filterNotNullTo(ArrayList<T>())
}

public fun <C : MutableCollection<in T>, T : Any> Iterable<T?>.filterNotNullTo(destination: C): C {
    for (element in this) if (element != null) destination.add(element)
    return destination
}
  • 3储玫、源碼解析

filterNotNull是集合的擴展函數侍筛,該集合中的元素是可null的T泛型,那么這個篩選條件也就是判斷是否為null,篩選條件內部確定好的撒穷。filterNotNull還是繼續(xù)傳入一個可變集合匣椰,然后在filterNotNullTo內部判斷把null的元素直接過濾,其他元素就會被加入傳入的可變集合中端礼。

  • 4禽笑、使用場景

filterNotNull操作符使用場景: 一般用于過濾掉集合中為null的元素弛车,最后返回一個不含null的元素集合。

filterNotNullTo操作符使用場景: 一般在外部傳入一個可變的集合蒲每,然后過濾多個集合中為null的元素纷跛,最后將這些元素放入可變集合中,并返回這個集合邀杏。

fun main(args: Array<String>) {
    val animalList: List<Animal?> = listOf(Bird(name = "Bird1", age = 12),
            Cat(name = "Cat1", age = 18),
            Cat(name = "Cat3", age = 20),
            Dog(name = "Dog2", age = 8),
            null,
            Bird(name = "Bird2", age = 14),
           null,
            Dog(name = "Dog1", age = 18)
    )

    animalList.filterNotNull().forEach { println("${it.name} is ${it.age} years old and it ${it.eatFood()}") }
}

<img src="https://user-gold-cdn.xitu.io/2018/5/10/16348c51b1f7c00f?w=357&h=245&f=png&s=42316"/>

drop操作符

  • 1贫奠、基本定義

根據傳入數值n,表示從左到右順序地刪除n個集合中的元素望蜡,并返回集合中剩余的元素唤崭。

  • 2、源碼定義
public fun <T> Iterable<T>.drop(n: Int): List<T> {
    require(n >= 0) { "Requested element count $n is less than zero." }
    if (n == 0) return toList()//要刪除元素為0脖律,說明剩余元素集合正好取整個集合
    val list: ArrayList<T>//聲明一個可變集合
    if (this is Collection<*>) {//如果原集合是一個只讀的Collection或者其子類谢肾,那么原集合的size是可確定的,那么創(chuàng)建新集合size是可以做差計算得到的
        val resultSize = size - n//拿原集合的size與起始下標做差值確定最終返回的集合的大小resultSize
        if (resultSize <= 0)//集合的size小于或等于0直接返回空集合
            return emptyList()
        if (resultSize == 1)//resultSize等于1說明就直接返回原集合的最后一個元素
            return listOf(last())
        list = ArrayList<T>(resultSize)//創(chuàng)建resultSize大小的可變集合
        if (this is List<T>) {
            if (this is RandomAccess) {//RandomAccess是一個集合標記接口,如果集合類是RandomAccess的實現(xiàn)小泉,則盡量用index下標 來遍歷而不要用Iterator迭代器來遍歷芦疏,在效率上要差一些。反過來微姊,如果List是Sequence List酸茴,則最好用迭代器來進行迭代。
                for (index in n until size)//采用下標遍歷
                    list.add(this[index])
            } else {
                for (item in listIterator(n))//采用迭代器遍歷
                    list.add(item)
            }
            return list
        }
    }
    else {//如果原集合是一個可變的集合兢交,那么就無法通過計算確切的新集合的size薪捍。
        list = ArrayList<T>()
    }
    var count = 0
    for (item in this) {
        if (count++ >= n) list.add(item)//對于可變集合通過遍歷,計數累加的方式配喳,當計數器超過起始下標就開始往集合中添加元素酪穿。
    }
    return list.optimizeReadOnlyList()
}
  • 3、原理圖解
image
  • 4晴裹、使用場景
    drop操作符一般是適用于把集合元素去除一部分被济,drop是順序的刪除廉沮,n則表示順序刪除幾個元素斟或,最后返回剩余元素集合
fun main(args: Array<String>) {
    val numberList = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9)
    numberList.drop(5).forEach { print("$it   ") }
}

<img src="https://user-gold-cdn.xitu.io/2018/5/10/16349728b200456d?w=379&h=102&f=png&s=19953"/>

dropLast操作符

  • 1、基本定義

根據傳入數值n揍瑟,表示從右到左倒序地刪除n個集合中的元素少欺,并返回集合中剩余的元素喳瓣。

  • 2、源碼定義
public fun <T> List<T>.dropLast(n: Int): List<T> {
    require(n >= 0) { "Requested element count $n is less than zero." }
    return take((size - n).coerceAtLeast(0))//這里應該是this.take(),this指代List,然后傳入(size - n)必須滿足大于或等于0
}

//這是一個Int類型的擴展函數赞别,用于判斷某個值是否大于傳入默認最小值,如果大于就直接返回這個值,否則返回這個默認最小值
public fun Int.coerceAtLeast(minimumValue: Int): Int {
    return if (this < minimumValue) minimumValue else this
}

//take也是一種操作符
public fun <T> Iterable<T>.take(n: Int): List<T> {
    require(n >= 0) { "Requested element count $n is less than zero." }
    if (n == 0) return emptyList()//這里n 實際上是size - dropLast傳入n的差值屿笼,n為0表示dropLast傳入n為原集合size,相當于刪除原集合size個數元素肝断,那么剩下就是空集合了
    if (this is Collection<T>) {//如果是一個只讀類型集合箫荡,就可以確定該集合的size
        if (n >= size) return toList()//如果這里n等于size表示dropLast傳入n為0利术,那么表示刪除集合元素個數為0,那么剩下來就是整個原集合了
        if (n == 1) return listOf(first())//如果n等于1轮蜕,表示dropLasr傳入n為size-1,那么表示刪除集合個數size-1個葱蝗,由于刪除順序是倒序的叮贩,自然原集合剩下的元素就是第一個元素了。
    }
    //以下是針對this是一個可變集合态坦,由于可變集合的size不太好確定攻旦,所以采用另一方式實現(xiàn)dropLast功能锋谐。
    var count = 0
    val list = ArrayList<T>(n)//創(chuàng)建剩余集合元素size大小n的可變集合
    for (item in this) {//由于是從右到左遞增刪除的惯退,取剩余从藤,現(xiàn)在是采用逆向方式催跪,從左到右加入新的集合中,一直等待count計數器自增到n為止夷野。
        if (count++ == n)
            break
        list.add(item)
    }
    return list.optimizeReadOnlyList()
}

  • 3懊蒸、原理圖解
image
  • 4、使用場景

使用的場景和drop相反悯搔,但是整體作用和drop類似骑丸。

fun main(args: Array<String>) {
    val strList = listOf("kotlin", "java", "javaScript", "C", "C++", "python", "Swift", "Go", "Scala")
    strList.dropLast(3).forEach { print("$it   ") }
}

<img src="https://user-gold-cdn.xitu.io/2018/5/10/1634aaacd337113c?w=394&h=110&f=png&s=22234"/>

dropWhile操作符

  • 1、基本定義

從集合的第一項開始去掉滿足條件元素妒貌,這樣操作一直持續(xù)到出現(xiàn)第一個不滿足條件元素出現(xiàn)為止通危,返回剩余元素(可能剩余元素有滿足條件的元素)

  • 2、源碼定義
public inline fun <T> Iterable<T>.dropWhile(predicate: (T) -> Boolean): List<T> {
    var yielding = false//初始化標志位false
    val list = ArrayList<T>()//創(chuàng)建一個新的可變集合
    for (item in this)//遍歷原集合
        if (yielding)//該標志一直為false直到灌曙,不符合lambda表達式外部傳入條件時菊碟,該標記為置為true,才開始往新集合添加元素
            list.add(item)
        else if (!predicate(item)) {//判斷不符合外部傳入的條件,才開始往新集合添加元素在刺,標記置為true逆害,
        //這樣就滿足了需求,一開始符合條件元素不會被添加到新集合中增炭,不符合條件才開始加入新集合忍燥,這樣產生新集合相對于原集合而言也就是刪除符合條件元素直到出現(xiàn)不符合條件的為止
            list.add(item)
            yielding = true
        }
    return list
}
  • 3、原理圖解
image
  • 4隙姿、使用場景

適用于去掉集合中前半部分具有相同特征的元素場景。

fun main(args: Array<String>) {
    val strList = listOf("java", "javaScript", "kotlin", "C", "C++", "javaFx","python", "Swift", "Go", "Scala")
    strList.dropWhile { it.startsWith("java") }.forEach { print("$it  ") }
}

<img src="https://user-gold-cdn.xitu.io/2018/5/11/1634ac993832748e?w=414&h=107&f=png&s=23617"/>

dropLastWhile操作符

  • 1厂捞、基本定義

從集合的最后一項開始去掉滿足條件元素输玷,這樣操作一直持續(xù)到出現(xiàn)第一個不滿足條件元素出現(xiàn)為止,返回剩余元素(可能剩余元素有滿足條件的元素)

  • 2靡馁、源碼定義
public inline fun <T> List<T>.dropLastWhile(predicate: (T) -> Boolean): List<T> {
    if (!isEmpty()) {
        val iterator = listIterator(size)//表示從原集合尾部開始向頭部迭代
        while (iterator.hasPrevious()) {//當前元素存在上一個元素進入迭代
            if (!predicate(iterator.previous())) {//直到出現(xiàn)上一個元素不符合條件欲鹏,才開始取相應后續(xù)元素,加入到新集合中
                return take(iterator.nextIndex() + 1)
            }
        }
    }
    return emptyList()
}
  • 3臭墨、原理圖解
image
  • 4赔嚎、使用場景

使用的場景和dropWhile類似,不過刪除元素順序不一樣

fun main(args: Array<String>) {
    val strList = listOf("java", "javaScript", "kotlin", "C", "C++", "javaFx", "python","Go", "Swift", "Scala")
    strList.dropLastWhile { it.startsWith("S") }.forEach { print("$it  ") }
}

<img src="https://user-gold-cdn.xitu.io/2018/5/11/1634ad3de26b3e72?w=410&h=112&f=png&s=23543"/>

take操作符

  • 1、基本定義

從原集合的第一項開始順序取集合的元素尤误,取n個元素侠畔,最后返回取出這些元素的集合。換句話說就是取集合前n個元素組成新的集合返回损晤。

  • 2软棺、源碼定義
public fun <T> Iterable<T>.take(n: Int): List<T> {
    require(n >= 0) { "Requested element count $n is less than zero." }
    if (n == 0) return emptyList()//n為0表示取0個元素的集合,返回空集合
    if (this is Collection<T>) {//如果是只讀集合尤勋,可確定集合的size
        if (n >= size) return toList()//如果要取元素集合大小大于或等于原集合大小那么就直接返回原集合
        if (n == 1) return listOf(first())//從第一項開始取1個元素喘落,所以就是集合的first()
    }
    var count = 0
    val list = ArrayList<T>(n)//創(chuàng)建一個n大小可變集合
    for (item in this) {//遍歷原集合
        if (count++ == n)//自增計數器count大小超過要取元素個數,就跳出循環(huán)
            break
        list.add(item)
    }
    return list.optimizeReadOnlyList()
}
  • 3最冰、原理圖解
image
  • 4瘦棋、使用場景

適用于順序從第一項開始取集合中子集合

fun main(args: Array<String>) {
    val strList = listOf("java", "javaScript", "kotlin", "C", "C++", "javaFx", "python","Go", "Swift", "Scala")
    strList.take(2).forEach { print("$it ") }
}

<img src="https://user-gold-cdn.xitu.io/2018/5/11/1634add5f729d35d?w=389&h=107&f=png&s=20564"/>

takeLast操作符

  • 1、基本定義

從原集合的最后一項開始倒序取集合的元素暖哨,取n個元素兽狭,最后返回取出這些元素的集合。

  • 2鹿蜀、源碼定義
public fun <T> List<T>.takeLast(n: Int): List<T> {
    require(n >= 0) { "Requested element count $n is less than zero." }
    if (n == 0) return emptyList()//n為0表示取0個元素的集合箕慧,返回空集合
    val size = size
    if (n >= size) return toList()//如果取的元素集合大小大于size直接返回整個集合
    if (n == 1) return listOf(last())//從最后一項開始取1個元素,自然就是返回last()
    val list = ArrayList<T>(n)//創(chuàng)建一個n大小的可變集合
    if (this is RandomAccess) {//RandomAccess是一個集合標記接口,如果集合類是RandomAccess的實現(xiàn)茴恰,則盡量用index下標 來遍歷而不要用Iterator迭代器來遍歷颠焦,在效率上要差一些。反過來往枣,如果List是Sequence List伐庭,則最好用迭代器來進行迭代。
        for (index in size - n until size)//采用下邊遍歷
            list.add(this[index])
    } else {
        for (item in listIterator(size - n))//采用迭代器遍歷
            list.add(item)
    }
    return list
}
  • 3分冈、原理圖解
image
  • 4圾另、使用場景

適用于倒序從最后一項開始取集合中子集合

fun main(args: Array<String>) {
    val strList = listOf("java", "javaScript", "kotlin", "C", "C++", "javaFx", "python","Go", "Swift", "Scala")
    strList.takeLast(2).forEach { print("$it ") }
}

<img src="https://user-gold-cdn.xitu.io/2018/5/11/1634ae67baee203c"/>

takeLastWhile操作符

  • 1、基本定義

從集合的最后一項開始取出滿足條件元素雕沉,這樣操作一直持續(xù)到出現(xiàn)第一個不滿足條件元素出現(xiàn)為止集乔,暫停取元素,返回取出元素的集合坡椒。

  • 2扰路、源碼定義
public inline fun <T> List<T>.takeLastWhile(predicate: (T) -> Boolean): List<T> {
    if (isEmpty())//如果當前集合是一個空的,那么直接返回空集合
        return emptyList()
    val iterator = listIterator(size)//表示從集合index = size開始迭代,那么size - 1也是最后一個元素倔叼,也即是迭代器的previous,也就是從集合尾部開始向頭部迭代
    while (iterator.hasPrevious()) {//含有上一個元素的元素繼續(xù)進入迭代
        if (!predicate(iterator.previous())) {//直到某個元素的前一個元素不符合條件汗唱,也是從最后一項開始遇到第一個不符合條件的元素,不進入以下操作
            iterator.next()
            val expectedSize = size - iterator.nextIndex()//由于從尾部開始迭代丈攒,那么符合條件元素集合的expectedSize等于原集合size與當前下一個元素的index的差值
            if (expectedSize == 0) return emptyList()//差值為0的話說明哩罪,在原集合尾部開始迭代就不符合條件被終止授霸,所以返回空集合
            return ArrayList<T>(expectedSize).apply {//拿到符合條件元素集合size,創(chuàng)建expectedSize大小新集合际插,并把迭代器中的元素遍歷加入到新集合中
                while (iterator.hasNext())
                    add(iterator.next())
            }
        }
    }
    return toList()
}
  • 3碘耳、源碼解析

takeLastWhile操作符是一個集合的擴展內聯(lián)函數,也是一個高階函數腹鹉,它接收一個以接收T泛型參數返回一個Boolean類型的Lambda表達式藏畅,也是即是takeLastWhile取元素的條件的實現(xiàn)。

  • 4功咒、原理圖解
image
  • 5愉阎、使用場景

適用于取出集合中后半部分具有相同特征的元素場景。

fun main(args: Array<String>) {
    val strList = listOf("java", "javaScript", "kotlin", "C", "C++", "javaFx", "python","Go", "Swift", "Scala")
    strList.takeLastWhile { it.startsWith("S") }.forEach { print("$it ") }
}

<img src="https://user-gold-cdn.xitu.io/2018/5/11/1634af0a8d2a7f5a?w=384&h=111&f=png&s=19956"/>

takeWhile操作符

  • 1力奋、基本定義

從集合的第一項開始取出滿足條件元素榜旦,這樣操作一直持續(xù)到出現(xiàn)第一個不滿足條件元素出現(xiàn)為止,暫停取元素景殷,返回取出元素的集合溅呢。

  • 2、源碼定義
public inline fun <T> Iterable<T>.takeWhile(predicate: (T) -> Boolean): List<T> {
    val list = ArrayList<T>()//創(chuàng)建一個可變集合
    for (item in this) {//遍歷原集合
        if (!predicate(item))//不符合傳入條件就直接跳出訓練
            break
        list.add(item)//符合條件的直接加入到新集合
    }
    return list//最后返回新集合
}
  • 3猿挚、源碼解析

takeWhile操作符是一個集合的擴展內聯(lián)函數咐旧,也是一個高階函數,它接收一個以接收T泛型參數返回一個Boolean類型的Lambda表達式绩蜻,也是即是takeWhile取元素的條件的實現(xiàn)铣墨。遍歷整個原集合,符合條件的加入到新集合中办绝,一旦遇到不符合條件元素直接跳出循環(huán)伊约,也就是遇到第一個不符合條件的就終止取元素的操作,最后返回這個新集合孕蝉。

  • 4屡律、原理圖解
image
  • 5、使用場景

適用于取出集合中前半部分具有相同特征的元素場景降淮。

fun main(args: Array<String>) {
    val strList = listOf("java", "javaScript", "kotlin", "C", "C++", "javaFx", "python","Go", "Swift", "Scala")
    strList.takeWhile { it.startsWith("java") }.forEach { print("$it ") }
}

<img src="https://user-gold-cdn.xitu.io/2018/5/11/1634af35174355ac?w=357&h=104&f=png&s=19685"/>

最后超埋,由于文章篇幅有限,上篇只詳細解析了過濾類的函數式API操作符骤肛,在下篇會繼續(xù)接著解析其他幾類操作符纳本,歡迎持續(xù)關注~~~

qrcode_for_gh_109398d5e616_430.jpg

歡迎關注Kotlin開發(fā)者聯(lián)盟,這里有最新Kotlin技術文章腋颠,每周會不定期翻譯一篇Kotlin國外技術文章。如果你也喜歡Kotlin吓笙,歡迎加入我們~~~

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末淑玫,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌絮蒿,老刑警劉巖尊搬,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異土涝,居然都是意外死亡佛寿,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門但壮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來冀泻,“玉大人,你說我怎么就攤上這事蜡饵〉妫” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵溯祸,是天一觀的道長肢专。 經常有香客問我,道長焦辅,這世上最難降的妖魔是什么博杖? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮筷登,結果婚禮上剃根,老公的妹妹穿的比我還像新娘。我一直安慰自己仆抵,他們只是感情好跟继,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著镣丑,像睡著了一般舔糖。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上莺匠,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天金吗,我揣著相機與錄音,去河邊找鬼趣竣。 笑死摇庙,一個胖子當著我的面吹牛,可吹牛的內容都是我干的遥缕。 我是一名探鬼主播卫袒,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼单匣!你這毒婦竟也來了夕凝?” 一聲冷哼從身側響起宝穗,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎码秉,沒想到半個月后逮矛,有當地人在樹林里發(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡转砖,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年须鼎,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片府蔗。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡晋控,死狀恐怖,靈堂內的尸體忽然破棺而出礁竞,到底是詐尸還是另有隱情糖荒,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布模捂,位于F島的核電站捶朵,受9級特大地震影響,放射性物質發(fā)生泄漏狂男。R本人自食惡果不足惜综看,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望岖食。 院中可真熱鬧红碑,春花似錦、人聲如沸泡垃。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蔑穴。三九已至忠寻,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間存和,已是汗流浹背奕剃。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留捐腿,地道東北人纵朋。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像茄袖,于是被迫代替她去往敵國和親操软。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內容