浅谈Kotlin中集合和函数式API完全解析-上篇(八)

简述: 今天带来的是Kotlin浅谈系列的第八讲,这讲我们一起来聊聊Kotlin这门语言对函数式编程的支持。我们都知道在kotlin这门语言中函数荣升成为了一等公民,所以在支持函数式编程的方面,Kotlin这门语言也是非常给力的,并且在Kotlin中语法也尽量推荐接近函数式编程的风格。学过以及了解过函数式编程的小伙伴都知道函数式编程最吸引人的地方,莫过于它拥有丰富的函数式操作符,可以使用一种全新的编程方式去操作集合数据。其中操作符最流行莫过于函数式中“三板斧”(过滤filter、映射map、折叠foldLeft/化约reduce)。那么小伙伴会提问了:

  • 1、那Kotlin语言中有这些操作符吗?

答: 当然有,不仅有这些而且还有很多很丰富的函数式操作符,仅从这方面来说Kotlin这门语言是函数式编程语言一点也不为过。

  • 2、那今天是讲函数式API吗?

答: 没错,今天会对Kotlin中所有函数式操作符API做详细的讲解,包括基本使用、基本定义、实质原理三个方面来做介绍,力求做到完全解析

  • 3、有什么小建议?

最后,一个小建议,由于Kotlin中的函数式API有很多,有些也不是经常使用的,建议先将经常使用的操作符(我会做好标记)理解、掌握。其他不常用的后续可以返回来查找即可

今天阐述的内容点很简单,主要有以下三点:

  • 1、Kotlin中集合的详细介绍和完全解析
  • 2、Kotlin中函数式API操作符的分类
  • 3、Kotlin中函数式API操作符的详解

一、Kotlin中集合的详细介绍和完全解析

在介绍函数式API操作符之前,有必要去了解一下这些操作符操作的对象集合。实际上,Kotlin语言中的集合和Java还是有一定区别的。在Kotlin中集合主要分为了两个大类,一类是可变集合(具有访问和修改权限),另一类是只读集合(只具有访问权限)(注意: 这里不能说不可变集合,只能说是具有可读权限,关于这个不可变和可读讨论之前博客有相关阐述)。Kotlin的集合设计与Java集合设计有一个很重要区别就是Kotlin把集合访问接口和集合修改接口分开了。

  • 1、Kotlin为什么把集合设计为可变和只读两种?

关于这个问题,实际上之前的var和val的分离设计已经回答了一部分。Kotlin这门考虑到实际开发中方便和程序中数据发生的事情更容易让人理解,所以才有此设计。我们设想一下这样的场景,kotlin中定义一个函数,函数的参数是一个可变集合,以kotlin开发规则而言,传递一个可变集合作为参数,实际上也在表明在该函数体内部涉及到修改集合操作。如果传递的是一个只读集合作为参数,那么表明在该函数体内是不会涉及到修改集合操作,只允许访问集合。看到如此的设计你是否已经爱上了这门语言,也就是这门语言在各个方面和开发细节上都是花了很多功夫的,力求做到任何一步都是让开发者开发更简单和更容易理解。

  • 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、可变集合与只读集合之间的区别和联系(以Collection集合为例)

Collection只读集合与MutableCollectio可变集合区别:

在Collection只具有访问元素的方法,不具有类似add、remove、clear之类的方法,而在MutableCollection中则相比Collection多出了修改元素的方法。

Collection只读集合与MutableCollectio可变集合联系:

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集合接口的实现类。把这种关系加上,上面的类关系图可以进一步完善。

image
  • 6、集合的初始化

由于在Kotlin中集合主要分为了只读集合和可变集合,那么初始化只读集合和可变集合的函数也不一样。以List集合为例,对于只读集合初始化一般采用listOf()方法对于可变集合初始化一般采用mutableListOf()或者直接创建ArrayList<E>,因为mutableListOf()内部实现也是也还是采用创建ArrayList,这个ArrayList实际上是Java中的java.util.ArrayList<E>,只不过在Kotlin中使用typealias(关于typealias的使用之前博客有过详细介绍)取了别名而已。关于具体内容请参考这个类kotlin.collections.TypeAliasesKt实现

  • 7、集合使用的注意事项

注意点一: 在代码的任何地方都优先使用只读集合,只在需要修改集合的情况下才去使用可变集合

注意点二: 只读集合不一定是不可变的,关于这个只读和不可变类似于val的只读和不可变原理。

注意点三: 不能把一个只读类型的集合作为参数传递给一个带可变类型集合的函数。

二、Kotlin中函数式API操作符的分类

Kotlin中函数式API操作符有很多,函数式中“三板斧”必须有的,定义和用法也是不尽相同。与其杂乱的死记硬背,不如先从大体上给这些API操作符分类,然后针对每一类去分析、理解、掌握,分类的规则也是按照各个操作符的功能来分。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对象的函数实现很简单,主要是通过IntRange对象拿到对应的start,end位置,然后利用subList拿到子集合,最后返回这个子集合。接收元素下标的集合的函数,是内部创建一个新的集合对象,然后遍历整个原集合把元素下标集合中的元素加入到新创建的集合中,最后返回这个新的集合对象。

  • 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>的扩展函数并且是一个内联函数,该函数接收一个以接收T类型返回一个Boolean类型的lambda表达式predicate作为参数,所以它还是一个高阶函数,返回一个List<T>集合

然后,看具体的内部实现是调用了另一个函数filterTo,并传入新创建的ArrayList<T>()可变集合对象,然后继续把lambda表达式作为参数传递到filterTo函数中,在filterTo函数去实现真正的过滤操作。传入的lambda表达式predicate实际上就是外部调用者传入的过滤条件,可以看到在filterTo内部是利用一个for循环进行筛选判断符合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几乎是一样的。他们之前唯一的区别是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实现原理还需要涉及两个操作符: filterIndexedTo、forEachIndexed。从整体上可以看出filterIndexed是一个Iterable<T>的扩展函数并且是一个内联函数,该函数接收一个以接收Int类型和接收T类型两个参数返回一个Boolean类型的lambda表达式predicate作为参数,所以它还是一个高阶函数,返回一个List<T>集合。

然后,大部分实现的原理和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是一个扩展函数,它的主要实现是借助于filterIsInstanceTo,通过外部传入的R泛型,创建一个R泛型的ArrayList可变集合,用于收集原集合中instance R类型的元素.可以看出在filterIsInstanceTo内部是遍历集合然后利用is判断属于R类型的元素就加入到集合中,最后返回该集合。

  • 4、使用场景

filterInstance使用场景: 适用于一个抽象类集合中还有多种子类型的元素,可以很方便筛选对应子类型的元素,并组成一个集合返回。

filterInstanceTo使用场景:
基本作用和filterInstance一致,不过唯一的区别就是这个可变集合ArrayList<R>不是在内部创建,而是由外部创建,非常适合筛选多个集合的情况。

下面看个例子,我们来看下不使用filterInstance和使用filterInstance情况对比。

没有使用filterInstance,而是使用filter和map集合相结合。(当你不知道有filterInstance操作符,估计很多都是这种实现的)

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操作符实现

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唯一区别就是判断条件取反

  • 4、原理图解
image
  • 5、使用场景

使用场景就是filter使用的取反条件使用,当然你也可以继续使用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还是继续传入一个可变集合,然后在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是可确定的,那么创建新集合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)//创建resultSize大小的可变集合
        if (this is List<T>) {
            if (this is RandomAccess) {//RandomAccess是一个集合标记接口,如果集合类是RandomAccess的实现,则尽量用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不太好确定,所以采用另一方式实现dropLast功能。
    var count = 0
    val list = ArrayList<T>(n)//创建剩余集合元素size大小n的可变集合
    for (item in this) {//由于是从右到左递增删除的,取剩余,现在是采用逆向方式,从左到右加入新的集合中,一直等待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、基本定义

从集合的第一项开始去掉满足条件元素,这样操作一直持续到出现第一个不满足条件元素出现为止,返回剩余元素(可能剩余元素有满足条件的元素)

  • 2、源码定义
public inline fun <T> Iterable<T>.dropWhile(predicate: (T) -> Boolean): List<T> {
    var yielding = false//初始化标志位false
    val list = ArrayList<T>()//创建一个新的可变集合
    for (item in this)//遍历原集合
        if (yielding)//该标志一直为false直到,不符合lambda表达式外部传入条件时,该标记为置为true,才开始往新集合添加元素
            list.add(item)
        else if (!predicate(item)) {//判断不符合外部传入的条件,才开始往新集合添加元素,标记置为true,
        //这样就满足了需求,一开始符合条件元素不会被添加到新集合中,不符合条件才开始加入新集合,这样产生新集合相对于原集合而言也就是删除符合条件元素直到出现不符合条件的为止
            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、基本定义

从集合的最后一项开始去掉满足条件元素这样操作一直持续到出现第一个不满足条件元素出现为止,返回剩余元素(可能剩余元素有满足条件的元素)

  • 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())) {//直到出现上一个元素不符合条件,才开始取相应后续元素,加入到新集合中
                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)//创建一个n大小可变集合
    for (item in this) {//遍历原集合
        if (count++ == n)//自增计数器count大小超过要取元素个数,就跳出循环
            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)//创建一个n大小的可变集合
    if (this is RandomAccess) {//RandomAccess是一个集合标记接口,如果集合类是RandomAccess的实现,则尽量用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、基本定义

从集合的最后一项开始取出满足条件元素这样操作一直持续到出现第一个不满足条件元素出现为止,暂停取元素,返回取出元素的集合。

  • 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()) {//含有上一个元素的元素继续进入迭代
        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,创建expectedSize大小新集合,并把迭代器中的元素遍历加入到新集合中
                while (iterator.hasNext())
                    add(iterator.next())
            }
        }
    }
    return toList()
}
  • 3、源码解析

takeLastWhile操作符是一个集合的扩展内联函数,也是一个高阶函数,它接收一个以接收T泛型参数返回一个Boolean类型的Lambda表达式,也是即是takeLastWhile取元素的条件的实现。

  • 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、基本定义

从集合的第一项开始取出满足条件元素这样操作一直持续到出现第一个不满足条件元素出现为止,暂停取元素,返回取出元素的集合。

  • 2、源码定义
public inline fun <T> Iterable<T>.takeWhile(predicate: (T) -> Boolean): List<T> {
    val list = ArrayList<T>()//创建一个可变集合
    for (item in this) {//遍历原集合
        if (!predicate(item))//不符合传入条件就直接跳出训练
            break
        list.add(item)//符合条件的直接加入到新集合
    }
    return list//最后返回新集合
}
  • 3、源码解析

takeWhile操作符是一个集合的扩展内联函数,也是一个高阶函数,它接收一个以接收T泛型参数返回一个Boolean类型的Lambda表达式,也是即是takeWhile取元素的条件的实现。遍历整个原集合,符合条件的加入到新集合中,一旦遇到不符合条件元素直接跳出循环,也就是遇到第一个不符合条件的就终止取元素的操作,最后返回这个新集合。

  • 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操作符,在下篇会继续接着解析其他几类操作符,欢迎持续关注~~~

qrcode_for_gh_109398d5e616_430.jpg

欢迎关注Kotlin开发者联盟,这里有最新Kotlin技术文章,每周会不定期翻译一篇Kotlin国外技术文章。如果你也喜欢Kotlin,欢迎加入我们~~~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,013评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,205评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,370评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,168评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,153评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,954评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,271评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,916评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,382评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,877评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,989评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,624评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,209评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,199评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,418评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,401评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,700评论 2 345

推荐阅读更多精彩内容