这节课我们来聊聊 Kotlin 这门语言对函数式编程的支持。我们都知道在 Kotlin 这门语言中函数荣升成为了一等公民,所以在支持函数式编程的方面,Kotlin 也是非常给力的,并且在 Kotlin 中语法也尽量推荐接近函数式编程的风格。
学过以及了解过函数式编程的小伙伴都知道函数式编程最吸引人的地方,莫过于它拥有丰富的函数式操作符,可以使用一种全新的编程方式去操作集合数据。其中操作符最流行莫过于函数式中“三板斧”(过滤filter、映射map、折叠foldLeft/化约reduce)。
Kotlin 中函数式 API 操作符有很多,函数式中“三板斧”必须有的,定义和用法也是不尽相同。与其杂乱的死记硬背,不如先从大体上给这些 API 操作符分类,然后针对每一类去分析、理解、掌握,分类的规则也是按照各个操作符的功能来分。Kotlin 中函数式 API 操作符主要有以下几大类。
Tips:由于篇幅问题,本节课我们会先将筛选类的操作符函数介绍完毕,其他操作函数后续文章进行讲解。
slice 操作符顾名思义是"切片"的意思,也就是它可以取集合中一部分元素或者某个元素,最后也是组合成一个新的集合。
它有两个重载函数,一个传入 IntRange 对象指定切片起始位置和终止位置,最后切出的是一个范围的元素加入到新集合中。另一个是传入一个 Iterable 下标集合,也就会从指定下标分别切出对应的元素,最后放入到新集合中。
源码定义
public fun List.slice(indices: IntRange): List {
if (indices.isEmpty()) return listOf()
return this.subList(indices.start, indices.endInclusive + 1).toList()
}
public fun List.slice(indices: Iterable): List {
val size = indices.collectionSizeOrDefault(10)
if (size == 0) return emptyList()
val list = ArrayList(size)
for (index in indices) {
list.add(get(index))
}
return list
}
源码解析
首先,slice 函数是 List
接收元素下标的集合的函数,是内部创建一个新的集合对象,然后遍历整个原集合把元素下标集合中的元素加入到新创建的集合中,最后返回这个新的集合对象。
使用场景
slice by IntRange 一般使用场景 :用于切取一段下标范围的子集合。
slice by itertar index 一般使用场景: 用于切取某个或者某些下标元素组成的集合。
fun main(args: Array) {
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 ")
}
}
根据用户定义的条件筛选集合中的数据,并且由此产生一个新的集合。这个新的集合是原集合的子集。
源码定义
public inline fun Iterable.filter(predicate: (T) -> Boolean): List {
return filterTo(ArrayList(), predicate)
}
public inline fun > Iterable.filterTo(destination: C, predicate: (T) -> Boolean): C {
for (element in this) if (predicate(element)) destination.add(element)
return destination
}
源码解析
首先,从整体上可以看出 filter 是一个 Iterable
然后,看具体的内部实现是调用了另一个函数 filterTo,并传入新创建的 ArrayList
原理图解
使用场景
filter的操作符使用场景:从一个集合筛选出符合条件的元素,并以一个新集合返回。
fun main(args: Array) {
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 ")}
}
filterTo 的操作符使用场景: 从多个集合筛选出符合条件的元素,并最终用一个集合进行收集从每个集合筛选出的元素。
fun main(args: Array) {
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().apply {
numberList1.filterTo(this) {
it % 2 == 0
}
numberList2.filterTo(this) {
it % 2 == 0
}
numberList3.filterTo(this) {
it % 2 == 0
}
}
print("从三个集合筛选出的偶数集合: ")
newNumberList.forEach {
print("$it ")
}
}
filterIndexed 操作符定义和 filter 几乎是一样的。他们之前唯一的区别是 filterIndexed 筛选条件的lambda 表达式多暴露一个参数那就是元素在集合中的 index。也就是外部可以拿到这个元素以及这个元素的 index. 特别适合需要集合元素 index 参与筛选条件的 case。
源码定义
public inline fun Iterable.filterIndexed(predicate: (index: Int, T) -> Boolean): List {
return filterIndexedTo(ArrayList(), predicate)
}
public inline fun > Iterable.filterIndexedTo(destination: C, predicate: (index: Int, T) -> Boolean): C {
forEachIndexed { index, element ->
if (predicate(index, element)) destination.add(element)
}
return destination
}
public inline fun Iterable.forEachIndexed(action: (index: Int, T) -> Unit): Unit {
var index = 0
for (item in this) action(index++, item)
}
源码解析
首先,要了解 filterIndexed 实现原理还需要涉及两个操作符: filterIndexedTo、forEachIndexed。从整体上可以看出 filterIndexed 是一个 Iterable
然后,大部分实现的原理和 filter 类似,filterIndexedTo 和 filterIndexed 类似,唯一可以说下的就是index,index 实际上是 forEachIndexed 内部的一个迭代自增计数器,可以在内部每次迭代,就会计数器就会自增一次,并且把这个 index 回调到外部。
使用场景
fun main(args: Array) {
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 ")
}
}
filterIsInstance 操作符是 filter 操作符一个特定应用,从集合中筛选出 instance 某个特定类型元素并把该元素强转成该类型,最后返回这些元素集合。
源码定义
public inline fun Iterable<*>.filterIsInstance(): List<@kotlin.internal.NoInfer R> {
return filterIsInstanceTo(ArrayList())
}
public inline fun > Iterable<*>.filterIsInstanceTo(destination: C): C {
for (element in this) if (element is R) destination.add(element)
return destination
}
源码解析
首先,filterIsInstance 是一个扩展函数,它的主要实现是借助于 filterIsInstanceTo,通过外部传入的R泛型,创建一个 R 泛型的 ArrayList 可变集合,用于收集原集合中 instance R 类型的元素.可以看出在filterIsInstanceTo 内部是遍历集合然后利用is判断属于 R 类型的元素就加入到集合中,最后返回该集合。
使用场景
filterInstance使用场景:适用于一个抽象类集合中还有多种子类型的元素,可以很方便筛选对应子类型的元素,并组成一个集合返回。
filterInstanceTo使用场景:
基本作用和 filterInstance 一致,不过唯一的区别就是这个可变集合 ArrayList
下面看个例子,我们来看下不使用 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) {
val animalList: List = 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) {
val animalList: List = 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().forEach { println("${it.name} is ${it.age} years old, and ${it.eatFood()}") }
}
从一个集合筛选出符合条件之外的元素,并以一个新集合返回,它是 filter 操作符取反操作。
源码定义
public inline fun Iterable.filterNot(predicate: (T) -> Boolean): List {
return filterNotTo(ArrayList(), predicate)
}
public inline fun > Iterable.filterNotTo(destination: C, predicate: (T) -> Boolean): C {
for (element in this) if (!predicate(element)) destination.add(element)
return destination
}
源码解析
实际上 filterNot 没什么可说的,它也是借助于 filterNotTo 操作具体,和 filterTo 唯一区别就是判断条件取反。
原理图解
使用场景
使用场景就是 filter 使用的取反条件使用,当然你也可以继续使用 filter 操作符,并且筛选条件为取反条件。
filterNotNull 操作符可以过滤集合中为 null 的元素,那么同理 filterNotNullTo 才是真正过滤操作,但是需要从外部传入一个可变集合。
源码定义
public fun Iterable.filterNotNull(): List {
return filterNotNullTo(ArrayList())
}
public fun , T : Any> Iterable.filterNotNullTo(destination: C): C {
for (element in this) if (element != null) destination.add(element)
return destination
}
源码解析
filterNotNull 是集合的扩展函数,该集合中的元素是可 null 的 T 泛型,那么这个筛选条件也就是判断是否为 null,筛选条件内部确定好的。filterNotNull 还是继续传入一个可变集合,然后在filterNotNullTo 内部判断把 null 的元素直接过滤,其他元素就会被加入传入的可变集合中。
使用场景
filterNotNull 操作符使用场景:一般用于过滤掉集合中为 null 的元素,最后返回一个不含 null 的元素集合。
filterNotNullTo 操作符使用场景: 一般在外部传入一个可变的集合,然后过滤多个集合中为 null 的元素,最后将这些元素放入可变集合中,并返回这个集合。
fun main(args: Array) {
val animalList: List = 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()}") }
}
根据传入数值 n,表示从左到右顺序地删除 n 个集合中的元素,并返回集合中剩余的元素。
源码定义
public fun Iterable.drop(n: Int): List {
require(n >= 0) { "Requested element count $n is less than zero." }
if (n == 0) return toList()//要删除元素为0,说明剩余元素集合正好取整个集合
val list: ArrayList//声明一个可变集合
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(resultSize)//创建resultSize大小的可变集合
if (this is List) {
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()
}
var count = 0
for (item in this) {
if (count++ >= n) list.add(item)//对于可变集合通过遍历,计数累加的方式,当计数器超过起始下标就开始往集合中添加元素。
}
return list.optimizeReadOnlyList()
}
原理图解
使用场景
drop 操作符一般是适用于把集合元素去除一部分,drop 是顺序的删除,n 则表示顺序删除几个元素,最后返回剩余元素集合
fun main(args: Array) {
val numberList = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9)
numberList.drop(5).forEach { print("$it ") }
}
根据传入数值 n,表示从右到左倒序地删除 n 个集合中的元素,并返回集合中剩余的元素。
源码定义
public fun List.dropLast(n: Int): List {
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 Iterable.take(n: Int): List {
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) {//如果是一个只读类型集合,就可以确定该集合的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(n)//创建剩余集合元素size大小n的可变集合
for (item in this) {//由于是从右到左递增删除的,取剩余,现在是采用逆向方式,从左到右加入新的集合中,一直等待count计数器自增到n为止。
if (count++ == n)
break
list.add(item)
}
return list.optimizeReadOnlyList()
}
原理图解
使用场景
使用的场景和 drop 相反,但是整体作用和 drop 类似。
fun main(args: Array) {
val strList = listOf("kotlin", "java", "javaScript", "C", "C++", "python", "Swift", "Go", "Scala")
strList.dropLast(3).forEach { print("$it ") }
}
从集合的第一项开始去掉满足条件元素,这样操作一直持续到出现第一个不满足条件元素出现为止,返回剩余元素(可能剩余元素有满足条件的元素)。
源码定义
public inline fun Iterable.dropWhile(predicate: (T) -> Boolean): List {
var yielding = false//初始化标志位false
val list = ArrayList()//创建一个新的可变集合
for (item in this)//遍历原集合
if (yielding)//该标志一直为false直到,不符合lambda表达式外部传入条件时,该标记为置为true,才开始往新集合添加元素
list.add(item)
else if (!predicate(item)) {//判断不符合外部传入的条件,才开始往新集合添加元素,标记置为true,
//这样就满足了需求,一开始符合条件元素不会被添加到新集合中,不符合条件才开始加入新集合,这样产生新集合相对于原集合而言也就是删除符合条件元素直到出现不符合条件的为止
list.add(item)
yielding = true
}
return list
}
原理图解
使用场景
适用于去掉集合中前半部分具有相同特征的元素场景。
fun main(args: Array) {
val strList = listOf("java", "javaScript", "kotlin", "C", "C++", "javaFx","python", "Swift", "Go", "Scala")
strList.dropWhile { it.startsWith("java") }.forEach { print("$it ") }
}
从集合的最后一项开始去掉满足条件元素,这样操作一直持续到出现第一个不满足条件元素出现为止,返回剩余元素(可能剩余元素有满足条件的元素)。
public inline fun List.dropLastWhile(predicate: (T) -> Boolean): List {
if (!isEmpty()) {
val iterator = listIterator(size)//表示从原集合尾部开始向头部迭代
while (iterator.hasPrevious()) {//当前元素存在上一个元素进入迭代
if (!predicate(iterator.previous())) {//直到出现上一个元素不符合条件,才开始取相应后续元素,加入到新集合中
return take(iterator.nextIndex() + 1)
}
}
}
return emptyList()
}
原理图解
使用场景
使用的场景和 dropWhile 类似,不过删除元素顺序不一样。
fun main(args: Array) {
val strList = listOf("java", "javaScript", "kotlin", "C", "C++", "javaFx", "python","Go", "Swift", "Scala")
strList.dropLastWhile { it.startsWith("S") }.forEach { print("$it ") }
}
从原集合的第一项开始顺序取集合的元素,取 n 个元素,最后返回取出这些元素的集合。换句话说就是取集合前 n 个元素组成新的集合返回。
源码定义
public fun Iterable.take(n: Int): List {
require(n >= 0) { "Requested element count $n is less than zero." }
if (n == 0) return emptyList()//n为0表示取0个元素的集合,返回空集合
if (this is Collection) {//如果是只读集合,可确定集合的size
if (n >= size) return toList()//如果要取元素集合大小大于或等于原集合大小那么就直接返回原集合
if (n == 1) return listOf(first())//从第一项开始取1个元素,所以就是集合的first()
}
var count = 0
val list = ArrayList(n)//创建一个n大小可变集合
for (item in this) {//遍历原集合
if (count++ == n)//自增计数器count大小超过要取元素个数,就跳出循环
break
list.add(item)
}
return list.optimizeReadOnlyList()
}
原理图解
使用场景
适用于顺序从第一项开始取集合中子集合:
fun main(args: Array) {
val strList = listOf("java", "javaScript", "kotlin", "C", "C++", "javaFx", "python","Go", "Swift", "Scala")
strList.take(2).forEach { print("$it ") }
}
从原集合的最后一项开始倒序取集合的元素,取 n 个元素,最后返回取出这些元素的集合。
源码定义
public fun List.takeLast(n: Int): List {
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(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
}
原理图解
使用场景
适用于倒序从最后一项开始取集合中子集合
fun main(args: Array) {
val strList = listOf("java", "javaScript", "kotlin", "C", "C++", "javaFx", "python","Go", "Swift", "Scala")
strList.takeLast(2).forEach { print("$it ") }
}
从集合的最后一项开始取出满足条件元素,这样操作一直持续到出现第一个不满足条件元素出现为止,暂停取元素,返回取出元素的集合。
源码定义
public inline fun List.takeLastWhile(predicate: (T) -> Boolean): List {
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(expectedSize).apply {//拿到符合条件元素集合size,创建expectedSize大小新集合,并把迭代器中的元素遍历加入到新集合中
while (iterator.hasNext())
add(iterator.next())
}
}
}
return toList()
}
源码解析
takeLastWhile 操作符是一个集合的扩展内联函数,也是一个高阶函数,它接收一个以接收T泛型参数返回一个 Boolean 类型的 Lambda 表达式,也是即是 takeLastWhile 取元素的条件的实现。
原理图解
使用场景
适用于取出集合中后半部分具有相同特征的元素场景。
fun main(args: Array) {
val strList = listOf("java", "javaScript", "kotlin", "C", "C++", "javaFx", "python","Go", "Swift", "Scala")
strList.takeLastWhile { it.startsWith("S") }.forEach { print("$it ") }
}
从集合的第一项开始取出满足条件元素,这样操作一直持续到出现第一个不满足条件元素出现为止,暂停取元素,返回取出元素的集合。
源码定义
public inline fun Iterable.takeWhile(predicate: (T) -> Boolean): List {
val list = ArrayList()//创建一个可变集合
for (item in this) {//遍历原集合
if (!predicate(item))//不符合传入条件就直接跳出训练
break
list.add(item)//符合条件的直接加入到新集合
}
return list//最后返回新集合
}
源码解析
takeWhile 操作符是一个集合的扩展内联函数,也是一个高阶函数,它接收一个以接收T泛型参数返回一个 Boolean 类型的 Lambda 表达式,也是即是 takeWhile 取元素的条件的实现。遍历整个原集合,符合条件的加入到新集合中,一旦遇到不符合条件元素直接跳出循环,也就是遇到第一个不符合条件的就终止取元素的操作,最后返回这个新集合。
原理图解
使用场景
适用于取出集合中前半部分具有相同特征的元素场景。
fun main(args: Array) {
val strList = listOf("java", "javaScript", "kotlin", "C", "C++", "javaFx", "python","Go", "Swift", "Scala")
strList.takeWhile { it.startsWith("java") }.forEach { print("$it ") }
}