kotlin系列3、函数加强之 高阶函数、内联函数、集合变换与序列、SAM转换、常用的高阶函数

本文是kotlin系列第3篇——函数加强。

  • 高阶函数 (将函数用作一个函数的参数或者返回值的函数
  • 内联函数(inline、crossinline、noinline,reified)
  • 集合转换与序列 (转数组,转集合,元素操作符,Sequences)
  • SAM函数/函数式接口
  • 常用的高阶函数 run、let、with、apply also

一、高阶函数

Kotlin中,高阶函数即指:将函数用作一个函数的参数或者返回值的函数

  • 在Kotlin中,函数可以用lambda或者函数引用来表示。
  • 因此,任何以lambda或者函数引用作为参数的函数,或者返回值为lambda或者函数引用的函数,或者两者都满足的函数都是高阶函数。

lambda的约定

要熟悉Kotlin函数,首先得看懂代码中的lambda表达式,这里首先就得清楚一些约定,如:

  • 当函数中只有一个函数作为参数,并且使用了lambda表达式作为对应的参数,那么可以省略函数的小括号 ()
  • 函数的最后一个参数是函数类型时,可以使用lambda表达式函数参数写在参数列表括号外面

可以省略函数的小括号()
例如:
str.sumBy( { it.toInt } )
可以省略成
str.sumBy{ it.toInt }

参数列表括号外面
Anko的Context扩展alert函数,可以注意到positiveButton方法第一个参数是text,
第二个参数是监听器lambda表达式,写在了参数列表圆括号外面。
alert("确定删除吗?","Alert") {
    positiveButton("OK") { Log.i(TAG, "你点了确定按钮")}
    negativeButton("Cancel") { Log.i(TAG, "你点了取消按钮") }
}.build().show()
复制代码
  • 在Kotlin中,变量的类型可以是函数类型,例如下面的代码中sum变量的类型是Int类型,而predicate变量是函数类型,也就是说这个变量代表一个函数
声明一个名字为sum的Int类型变量(这个sum变量的类型是Intvar sum:Int

声明一个名字为predicate的函数类型变量(这个predicate变量的类型是函数)
predicate是一个以Char为参数,返回值为Boolean的函数。
var predicate: (Char) -> Boolean

声明一个以predicate函数为参数的函数(高阶函数),这个函数的返回类型是String
fun filter(predicate: (Char) -> Boolean) :String

让上面这个函数带上接受者,其实就是给String声明了一个扩展函数。
带上了接收者的函数,函数内部可以直接访问String的其他方法属性,相当于函数内部的this就是String
fun String.filter(predicate: (char) -> Boolean) :String
复制代码

将函数用作函数参数的情况的高阶函数

这里介绍字符串中的sumBy{}高阶函数

// sumBy函数的源码
public inline fun CharSequence.sumBy(selector: (Char) -> Int): Int {
    var sum: Int = 0
    for (element in this) {
        sum += selector(element)
    }
    return sum
}
复制代码
  1. 该函数返回一个Int类型的值。并且接受了一个selector()函数作为该函数的参数。其中,selector()函数接受一个Char类型的参数,并且返回一个Int类型的值。
  2. 定义一个sum变量,并且循环这个字符串,循环一次调用一次selector()函数并加上sum。用作累加。其中this关键字代表字符串本身。

所以这个函数的作用是:把字符串中的每一个字符转换为Int的值,用于累加,最后返回累加的值

val testStr = "abc"
val sum = testStr.sumBy { it.toInt() }
println(sum)

输出:
294 // 因为字符a对应的值为97,b对应98,c对应99,故而该值即为 97 + 98 + 99 = 294
复制代码

将函数用作一个函数的返回值的高阶函数

来个例子

fun testMethod() : (str : String) -> Int{
 return { str ->
    str.length
  }
}
复制代码

二、内联函数

使用方式:inline fun 方法名

内联函数:在程序编译时能将程序中内联函数的调用表达式直接替换成内联函数的函数体。换成人话,就是: 把函数体复制粘贴到函数调用处

听着是不是有点绕?上代码你就知道了。

一个内联函数:

fun test() {
  var x = 1
  hello()
  print(x)
}

inline fun hello() {
    var a = 1
    var b = 1
    var c = a + b
    print(c)
}
复制代码

在编译器上编写看到的代码,而实际编译的时候会变成

fun test() {
  var x = 1
  var a = 1
  var b = 1
  var c = a + b
  print(c)
  print(x)
}
复制代码

但实际上如果你这么写,你会发现编译器会给你一个警告提醒,大致意思是说不建议你在这里使用内联函数。

内联函数需要知道的点

  • inline函数 一般和Lambda结合使用在高阶函数里
  • 内联函数有利于提升性能,写高阶函数有可能的话尽量写成内联(弥补高阶函数中 Lambda 带来的额外运行开销的)
  • 非高阶函数,一般不要内联函数
  • 一个高阶函数一旦被标记为内联,它的方法体和所有 Lambda 参数都会被内联
  • 当你声明一个函数为内联函数的时候,这个函数所使用的lambda(也就是函数)也默认为内联函数。
  • 如果我们想让内联函数中的 lambda 不成为内联函数,可以用  noinline ,它可以避免该 Lambda 被内联
  • 普通的 Lambda 不支持非局部返回,内联之后允许非局部返回。既要内联,又要禁止非局部返回,请使用 crossinline

为什么内联函数能提升性能?

使用高阶函数会带来一些运行时的效率损失:每一个函数都是一个对象,并且会捕获一个闭包。 即那些在函数体内会访问到的变量。 内存分配(对于函数对象和类)和虚拟调用会引入运行时间开销,但是通过内联化 Lambda 表达式可以消除这类的开销。

为了解决这个问题,可以使用内联函数,用inline修饰的函数就是内联函数,inline修饰符影响函数本身和传给它的 Lambda 表达式,所有这些都将内联到调用处,即编译器会把调用这个函数的地方用这个函数的方法体进行替换,而不是创建一个函数对象并生成一个调用。

换成人话:我们在写代码的时候难免会遇到这种情况,就是很多处的代码是一样的,于是乎我们就会抽取出一个公共方法来进行调用,这样看起来就会很简洁;但是也出现了一个问题,就是这个方法会被频繁调用,就会很耗费资源。而内联函数就解决了这个问题。内联函数,其原理就是:在编译时期,把调用这个函数的地方用这个函数的方法体进行替换

在kotlin中,函数就是对象,当你调用某个函数的时候,就会创建相关的对象。这就是空间上的开销!

再来点例子

内联函数的基本示例

inline fun inlineFunction(myFun: () -> Unit ) {
    myFun()
    print("内联函数内的代码")
}

fun main(args: Array<String>) {
    inlineFunction({ println("调用内联函数")})
}

输出:
调用内联函数
内联函数内的代码
复制代码

非局部控制流程

内联函数,默认允许从lambda表达式本身返回。 这也将导致退出调用内联函数。

在这种情况下,允许内联函数的lanmda函数具有非局部返回语句。

inline fun inlineFunction(myFun: () -> Unit, nxtFun: () -> Unit) {
    myFun()
    nxtFun()
    print("内联函数内的代码")
}

fun main(args: Array<String>) {
    inlineFunction(
        { 
            println("调用内联函数") 
            return
        },
        { 
            println("内联函数中的下一个参数")
        }
    )
}

输出:
调用内联函数
复制代码

crossinline 注解

  • 要防止从lambda表达式和内联函数本身返回(return),可以将lambda表达式标记为crossinline。 如果在lambda表达式中找到了return语句,则会抛出编译器错误。

  • 另外,crossinline还可以让内联函数的lambda表达式 作为参数传递。

inline fun inlineFunction(crossinline myFun: () -> Unit, nxtFun: () -> Unit) {
    myFun()
    nxtFun()
    print("内联函数内的代码")
}


fun main(args: Array<String>) {
    inlineFunction(
        {
            println("调用内联函数")
            //return // 写上会报错
        },
        { 
            println("内联函数中的下一个参数") 
        }
    )
}

输出:
调用内联函数
内联函数中的下一个参数
内联函数内的代码
复制代码

内联之后lambda允许非局部返回(普通的 Lambda 不支持非局部返回)。既要内联,又要禁止非局部返回,请使用 crossinline

noinline修饰符

如果我们想让内联函数中的 lambda 不成为内联函数,可以用  noinline ,它可以避免该 Lambda 被内联

也可以说,noinline之后,内联函数里的 lambda(参数) 就不再是内联函数,就可以作为参数,如果需要亦可作为返回值

// 注意:noinline nxtFun
// 内联的函数的的参数里函数都是内联函数,但是通过noinline可以解除限制
inline fun inlineFunctionExample(myFun: () -> Unit, noinline nxtFun: () -> Unit) {
    myFun()
    nxtFun()
    
    // 核心对比
    //show({ myFun() }) // 报错,因为内联函数 不能再作为函数的参数(传参)或者返回值
    show({ nxtFun() }) // 可以,因为nxtFun被noinline修饰了,不再是内联函数了
    
    println("内联函数内的代码")
}

fun show(simpleFun: () -> Unit){
    simpleFun()
    println("调用show方法")
}

fun main(args: Array<String>) {
    inlineFunctionExample(
        {
            println("调用内联函数")
        },
        {
            println("内联函数中的下一个参数")
        }
    )
    println("这是关闭main函数")
}

输出:
调用内联函数
内联函数中的下一个参数
内联函数中的下一个参数
调用show方法
内联函数内的代码
这是关闭main函数

复制代码

noinline 和 crossinline 的对比

这个时候,有必要来一波对比了。
默认情况下内联函数里的 lambda 函数参数,他们也被内联成内联函数了,他们是这样限制的:

  • 内联函数的 lambda 函数 允许lambda表达式具有非局部返回语句(return)
  • 内联函数的 lambda 函数 不允许作为参数传递

加上 crossinline 和 noinline之后呢?

  • crossinline:不能有函数的返回值的,禁止局部返回,允许作为参数传递。(既要内联,又要禁止非局部返回)
  • noinline: 不再是内联函数,就可以作为参数,如果需要亦可作为返回值

reified 和 泛型

reifie 本身是 具体化 的意思

  • reified:使抽象的东西更加具体或真实,非常推荐 Android 开发使用这个关键字。

reified关键字是用于Kotlin内联函数的,修饰内联函数的泛型,泛型被修饰后,在方法体里,能从泛型拿到泛型的Class对象

  • reified 关键字只能同内联函数一起使用

例子

上栗子

inline fun <reified T> printType() {
  print(T::class.java)
}

fun printStringType(){
 // 用 String 类型调用被 reified 修饰的泛型函数
  printType<String>()  
}
复制代码

在来个例子

记住我们之前说的,reified:使抽象的东西更加具体或真实。

我们来对比一下,startActivity用reified和没用的区别

  • 没有使用reified的版本
// 没有使用reified的版本
// Function
private fun <T : Activity> Activity.startActivity(context: Context, clazz: Class<T>) {
    startActivity(Intent(context, clazz))
}

// Caller
startActivity(context, NewActivity::class.java)
复制代码

.
.

  • 使用reified的版本
// 使用reified的版本
// Function
inline fun <reified T : Activity> Activity.startActivity(context: Context) {
    startActivity(Intent(context, T::class.java))
}

// Caller
startActivity<NewActivity>(context)
复制代码

.
.
接下来,再看两个例子

使用 reified,简化泛型参数和保证 as? 类型转换安全性 的例子

// Function
private inline fun <reified T> Bundle.getDataOrNull(): T? {
    return getSerializable(DATA_KEY) as? T
}

// Caller
val bundle: Bundle? = Bundle()
bundle?.putSerializable(DATA_KEY, "Testing")
val strData: String? = bundle?.getDataOrNull()
val intData: Int? = bundle?.getDataOrNull() // Null
复制代码

.
.

使用 reified,可以实现不同的返回类型函数重载

inline fun <reified T> Resources.dpToPx(value: Int): T {
    val result = TypedValue.applyDimension(
        TypedValue.COMPLEX_UNIT_DIP,
        value.toFloat(), displayMetrics)

    return when (T::class) {
        Float::class -> result as T
        Int::class -> result.toInt() as T
        else -> throw IllegalStateException("Type not supported")
    }
}
复制代码

// Caller
val intValue: Int = resource.dpToPx(64)
val floatValue: Float = resource.dpToPx(64)
.
.
关于reified的原理可以看这个文章
zhuanlan.zhihu.com/p/255198214

三、集合转换与序列

三.1 集合转换

三.1.1 转换类

关于集合转换的函数,其实基本都是高阶函数

Kotlin 标准库为集合 转换 提供了一组扩展函数。 这些函数根据提供的转换规则从现有集合中构建新集合。

三.1.1.1 转为数组

当我们声明一个集合,可以把这个集合根据调用集合类相应的高阶函数来转换成相应的数组。集合类提供了

  • toIntArray()
  • toDoubleArray()
  • toFloatArray()
  • toBetArray()

等高阶函数去处理。

fun listToArray(){
    val list = listOf<Int>(1,2,3,4,5,6)         // 声明一个Int类型的List
    val listArray = list.toIntArray()           // 转换

    println(list.javaClass.toString())          // 打印list的类型
    println(listArray.javaClass.toString())     // 打印listArray的类型
    println(listArray[1])
}

fun main(args: Array<String>) {
    listToArray()
}

输出:
class java.util.Arrays$ArrayList
class [I
2
复制代码

三.1.1.2 转换为集合

Kotlin中,集合可分为不可变集合与可变集合。

我们声明一个集合或者数组,可以转换成相应类型的集合。

  • 调用toXXX()转换成不可变集合。

调用toMutableXXX()转换为可变集合。

集合类提供了

  • toList()
  • toMutableList()
  • toSet()
  • toMutableSet()
  • toHashSet()
  • toMap()等高阶函数去处理。
// 数组转集合
fun arrayToList() {
    val arr = arrayOf(1,3,5,7,9)
    val list = arr.toList()
    println("变量arr的类型为:${arr.javaClass}")
    println("变量list的类型为:${list.javaClass}")
    println(list[1])
}

// 集合转集合,这里用Set转List

fun listToList(){
    val set = setOf(1)
    val setTolist = set.toList()

    println("变量set的类型为:${set.javaClass}")
    println("变量setTolist的类型为:${setTolist.javaClass}")
    println(setTolist[0])
}

fun main(args: Array<String>) {
    arrayToList()
    listToList()
}

输出:
变量arr的类型为:class [Ljava.lang.Integer;
变量list的类型为:class java.util.ArrayList
3

变量set的类型为:class java.util.Collections$SingletonSet
变量setTolist的类型为:class java.util.Collections$SingletonList
1
复制代码

三.1.2 操作类

主要分为6大类。

  • 元素操作符
  • 顺序操作符
  • 映射操作符
  • 过滤操作符
  • 生产操作符
  • 统计操作符

三.1.2.1 元素操作符

  • contains(元素) : 检查集合中是否包含指定的元素,若存在则返回true,反之返回false
  • elementAt(index) : 获取对应下标的元素。若下标越界,会抛出IndexOutOfBoundsException(下标越界)异常,同get(index)一样
  • elementAtOrElse(index,{...}) : 获取对应下标的元素。若下标越界,返回默认值,此默认值就是你传入的下标的运算值
  • elementAtOrNull(index) : 获取对应下标的元素。若下标越界,返回null
  • first() : 获取第一个元素,若集合为空集合,这会抛出NoSuchElementException异常
  • first{} : 获取指定元素的第一个元素。若不满足条件,则抛出NoSuchElementException异常
  • firstOrNull() : 获取第一个元素,若集合为空集合,返回null
  • firstOrNull{} : 获取指定元素的第一个元素。若不满足条件,返回null
  • getOrElse(index,{...}) : 同elementAtOrElse一样
  • getOrNull(index) : 同elementAtOrNull一样
  • last() : 同first()相反
  • last{} : 同first{}相反
  • lastOrNull{} : 同firstOrNull()相反
  • lastOrNull() : 同firstOrNull{}相反
  • indexOf(元素) : 返回指定元素的下标,若不存在,则返回-1
  • indexOfFirst{...} : 返回第一个满足条件元素的下标,若不存在,则返回-1
  • indexOfLast{...} : 返回最后一个满足条件元素的下标,若不存在,则返回-1
  • single() : 若集合的长度等于0,则抛出NoSuchElementException异常,若等于1,则返回第一个元素。反之,则抛出IllegalArgumentException异常
  • single{} : 找到集合中满足条件的元素,若元素满足条件,则返回该元素。否则会根据不同的条件,抛出异常。这个方法慎用
  • singleOrNull() : 若集合的长度等于1,则返回第一个元素。否则,返回null
  • singleOrNull{} : 找到集合中满足条件的元素,若元素满足条件,则返回该元素。否则返回null
  • forEach{...} : 遍历元素。一般用作元素的打印
  • forEachIndexed{index,value} : 遍历元素,可获得集合中元素的下标。一般用作元素以及下标的打印
  • componentX() : 这个函数在前面的章节中提过多次了。用于获取元素。其中的X只能代表1..5。详情可看下面的例子

.
.

来个例子

fun test(){
    val list = listOf("kotlin","Android","Java","PHP","Python","IOS")

    println("  ------   contains -------")
    println(list.contains("JS"))

    println("  ------   elementAt -------")

    println(list.elementAt(2))
    println(list.elementAtOrElse(10,{it}))
    println(list.elementAtOrNull(10))

    println("  ------   get -------")
    println(list.get(2))
    println(list.getOrElse(10,{it}))
    println(list.getOrNull(10))

    println("  ------   first -------")
    println(list.first())
    println(list.first{ it == "Android" })
    println(list.firstOrNull())
    println(list.firstOrNull { it == "Android" })

    println("  ------   last -------")
    println(list.last())
    println(list.last{ it == "Android" })
    println(list.lastOrNull())
    println(list.lastOrNull { it == "Android" })

    println("  ------   indexOf -------")
    println(list.indexOf("Android"))
    println(list.indexOfFirst { it == "Android" })
    println(list.indexOfLast { it == "Android" })

    println("  ------   single -------")
    val list2 = listOf("list")
    println(list2.single())     // 只有当集合只有一个元素时,才去用这个函数,不然都会抛出异常。
    println(list2.single { it == "list" }) //当集合中的元素满足条件时,才去用这个函数,不然都会抛出异常。若满足条件返回该元素
    println(list2.singleOrNull()) // 只有当集合只有一个元素时,才去用这个函数,不然都会返回null。
    println(list2.singleOrNull { it == "list" }) //当集合中的元素满足条件时,才去用这个函数,不然返回null。若满足条件返回该元素

    println("  ------   forEach -------")
    list.forEach { println(it) }
    list.forEachIndexed { index, it -> println("index : $index \t value = $it") }

    println("  ------   componentX -------")
    println(list.component1())  // 等价于`list[0]  <=> list.get(0)`
    println(list.component2())  // 等价于`list[1]  <=> list.get(1)`
    println(list.component3())  // 等价于`list[2]  <=> list.get(2)`
    println(list.component4())  // 等价于`list[3]  <=> list.get(3)`
    println(list.component5())  // 等价于`list[4]  <=> list.get(4)`
}
复制代码

.
输出

  ------   contains -------
false
  ------   elementAt -------
Java
10
null
  ------   get -------
Java
10
null
  ------   first -------
kotlin
Android
kotlin
Android
  ------   last -------
IOS
Android
IOS
Android
  ------   indexOf -------
1
1
1
  ------   single -------
list
list
list
list
  ------   forEach -------
kotlin
Android
Java
PHP
Python
IOS
index : 0 	 value = kotlin
index : 1 	 value = Android
index : 2 	 value = Java
index : 3 	 value = PHP
index : 4 	 value = Python
index : 5 	 value = IOS
  ------   componentX -------
kotlin
Android
Java
PHP
Python
复制代码

.
.

三.1.2.2 顺序操作符

  • reversed() : 反序。即和初始化的顺序反过来。
  • sorted() : 自然升序。
  • sortedBy{} : 根据条件升序,即把不满足条件的放在前面,满足条件的放在后面
  • sortedDescending() : 自然降序。
  • sortedByDescending{} : 根据条件降序。和sortedBy{}相反

来个例子

fun test() {
    val list1 = listOf(-1, -3, 1, 3, 5, 6, 7, 2, 4, 10, 9, 8)

    // 反序
    println(list1.reversed())

    // 升序
    println(list1.sorted())

    // 根据条件升序,即把不满足条件的放在前面,满足条件的放在后面
    println(list1.sortedBy { it % 2 == 0 })

    // 降序
    println(list1.sortedDescending())

    // 根据条件降序,和`sortedBy{}`相反
    println(list1.sortedByDescending { it % 2 == 0 })
}
复制代码

输出:

[8, 9, 10, 4, 2, 7, 6, 5, 3, 1, -3, -1]
[-3, -1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[-1, -3, 1, 3, 5, 7, 9, 6, 2, 4, 10, 8]
[10, 9, 8, 7, 6, 5, 4, 3, 2, 1, -1, -3]
[6, 2, 4, 10, 8, -1, -3, 1, 3, 5, 7, 9]
复制代码

.
.

三.1.2.3 映射操作符

  • map{...} : 把每个元素按照特定的方法进行转换,组成一个新的集合。
  • mapNotNull{...} : 同map{}函数的作用相同,只是过滤掉转换之后为null的元素
  • mapIndexed{index,result} : 把每个元素按照特定的方法进行转换,只是其可以操作元素的下标(index),组成一个新的集合。
  • mapIndexedNotNull{index,result} : 同mapIndexed{}函数的作用相同,只是过滤掉转换之后为null的元素
  • flatMap{...} : 根据条件合并两个集合,组成一个新的集合。
  • groupBy{...} : 分组。即根据条件把集合拆分为为一个Map<K,List<T>>类型的集合。具体看实例

.

来个例子

fun test() {
    val list1 = listOf("kotlin","Android","Java","PHP","JavaScript")
    // map{...} : 把每个元素按照特定的方法进行转换,组成一个新的集合。
    println(list1.map { "str-".plus(it) })

    // mapNotNull{...} : 同map{}函数的作用相同,只是过滤掉转换之后为null的元素
    println(list1.mapNotNull { "str-".plus(it) })

    //mapIndexed{index,result} : 把每个元素按照特定的方法进行转换,只是其可以操作元素的下标(index),组成一个新的集合。
    println(list1.mapIndexed { index, str ->
        index.toString().plus("-").plus(str)
    })

    //mapIndexedNotNull{index,result} : 同mapIndexed{}函数的作用相同,只是过滤掉转换之后为null的元素
    println(list1.mapIndexedNotNull { index, str ->
        index.toString().plus("-").plus(str)
    })

    // flatMap{...} : 根据条件合并两个集合,组成一个新的集合。
    println( list1.flatMap { listOf(it,"new-".plus(it)) })

    // groupBy{...} : 分组。即根据条件把集合拆分为为一个Map<K,List<T>>类型的集合。
    println(list1.groupBy { if (it.startsWith("Java")) "big" else "latter" })
}
复制代码

.
输出

[str-kotlin, str-Android, str-Java, str-PHP, str-JavaScript]
[str-kotlin, str-Android, str-Java, str-PHP, str-JavaScript]
[0-kotlin, 1-Android, 2-Java, 3-PHP, 4-JavaScript]
[0-kotlin, 1-Android, 2-Java, 3-PHP, 4-JavaScript]
[kotlin, new-kotlin, Android, new-Android, Java, new-Java, PHP, new-PHP, JavaScript, new-JavaScript]
{latter=[kotlin, Android, PHP], big=[Java, JavaScript]}
复制代码

.
.

三.1.2.4 过滤操作符

  • filter{...} : 把不满足条件的元素过滤掉
  • filterIndexed{...} : 和filter{}函数作用类似,只是可以操作集合中元素的下标(index
  • filterNot{...} : 和filter{}函数的作用相反
  • filterNotNull() : 过滤掉集合中为null的元素。
  • take(num) : 返回集合中前num个元素组成的集合
  • takeWhile{...} : 循环遍历集合,从第一个元素开始遍历集合,当第一个出现不满足条件元素的时候,退出遍历。然后把满足条件所有元素组成的集合返回。
  • takeLast(num) : 返回集合中后num个元素组成的集合
  • takeLastWhile{...} : 循环遍历集合,从最后一个元素开始遍历集合,当第一个出现不满足条件元素的时候,退出遍历。然后把满足条件所有元素组成的集合返回。
  • drop(num) : 过滤集合中前num个元素
  • dropWhile{...} : 相同条件下,和执行takeWhile{...}函数后得到的结果相反
  • dropLast(num) : 过滤集合中后num个元素
  • dropLastWhile{...} : 相同条件下,和执行takeLastWhile{...}函数后得到的结果相反
  • distinct() : 去除重复元素
  • distinctBy{...} : 根据操作元素后的结果去除重复元素
  • slice : 过滤掉所有不满足执行下标的元素。

.

来个例子

fun test() {
    val list1 = listOf(-1,-3,1,3,5,6,7,2,4,10,9,8)
    val list2 = listOf(1,3,4,5,null,6,null,10)
    val list3 = listOf(1,1,5,2,2,6,3,3,7,4,4,8)

    println("  ------   filter -------")
    println(list1.filter { it > 1  })
    println(list1.filterIndexed { index, result ->
        index < 5 && result > 3
    })
    println(list1.filterNot { it > 1 })
    println(list2.filterNotNull())

    println("  ------   take -------")
    println(list1.take(5))
    println(list1.takeWhile { it < 5 })
    println(list1.takeLast(5))
    println(list1.takeLastWhile { it > 5 })

    println("  ------   drop -------")
    println(list1.drop(5))
    println(list1.dropWhile { it < 5 })
    println(list1.dropLast(5))
    println(list1.dropLastWhile { it > 5 })

    println("  ------   distinct -------")
    println(list3.distinct())
    println(list3.distinctBy { it + 2 })

    println("  ------   slice -------")
    println(list1.slice(listOf(1,3,5,7)))
    println(list1.slice(IntRange(1,5)))
}

fun main(args: Array<String>) {
    test()
}
复制代码

.
输出

  ------   filter -------
[3, 5, 6, 7, 2, 4, 10, 9, 8]
[5]
[-1, -3, 1]
[1, 3, 4, 5, 6, 10]
  ------   take -------
[-1, -3, 1, 3, 5]
[-1, -3, 1, 3]
[2, 4, 10, 9, 8]
[10, 9, 8]
  ------   drop -------
[6, 7, 2, 4, 10, 9, 8]
[5, 6, 7, 2, 4, 10, 9, 8]
[-1, -3, 1, 3, 5, 6, 7]
[-1, -3, 1, 3, 5, 6, 7, 2, 4]
  ------   distinct -------
[1, 5, 2, 6, 3, 7, 4, 8]
[1, 5, 2, 6, 3, 7, 4, 8]
  ------   slice -------
[-3, 3, 6, 2]
[-3, 1, 3, 5, 6]
复制代码

.
.

三.1.2.5 生产操作符

  • plus() : 合并两个集合中的元素,组成一个新的集合。也可以使用符号+
  • zip : 由两个集合按照相同的下标组成一个新集合。该新集合的类型是:List<Pair>
  • unzip : 和zip的作用相反。把一个类型为List<Pair>的集合拆分为两个集合。看下面的例子
  • partition : 判断元素是否满足条件把集合拆分为有两个Pair组成的新集合。
fun test() {
    val list1 = listOf(1,2,3,4)
    val list2 = listOf("kotlin","Android","Java","PHP","JavaScript")

    // plus() 和 `+`一样
    println(list1.plus(list2))
    println(list1 + list2)

    // zip
    println(list1.zip(list2))
    println(list1.zip(list2){       // 组成的新集合由元素少的原集合决定
            it1,it2-> it1.toString().plus("-").plus(it2)
    })

    // unzip
    val newList = listOf(Pair(1,"Kotlin"),Pair(2,"Android"),Pair(3,"Java"),Pair(4,"PHP"))
    println(newList.unzip())

    // partition
    println(list2.partition { it.startsWith("Ja") })
}

fun main(args: Array<String>) {
    test()
}
复制代码

输出:

[1, 2, 3, 4, kotlin, Android, Java, PHP, JavaScript]
[1, 2, 3, 4, kotlin, Android, Java, PHP, JavaScript]
[(1, kotlin), (2, Android), (3, Java), (4, PHP)]
[1-kotlin, 2-Android, 3-Java, 4-PHP]
([1, 2, 3, 4], [Kotlin, Android, Java, PHP])
([Java, JavaScript], [kotlin, Android, PHP])
复制代码

.
.

三.1.2.6 统计操作符

  • any() : 判断是不是一个集合,若是,则在判断集合是否为空,若为空则返回false,反之返回true,若不是集合,则返回hasNext
  • any{...} : 判断集合中是否存在满足条件的元素。若存在则返回true,反之返回false
  • all{...} : 判断集合中的所有元素是否都满足条件。若是则返回true,反之则返回false
  • none() : 和any()函数的作用相反
  • none{...} : 和all{...}函数的作用相反
  • maxOrNull() : 获取集合中最大的元素,若为空元素集合,则返回null
  • maxByOrNull{...} : 获取方法处理后返回结果最大值对应那个元素的初始值,如果没有则返回null
  • minOrNull() : 获取集合中最小的元素,若为空元素集合,则返回null
  • minByOrNull{...} : 获取方法处理后返回结果最小值对应那个元素的初始值,如果没有则返回null
  • sum() : 计算出集合元素累加的结果。
  • sumBy{...} : 根据元素运算操作后的结果,然后根据这个结果计算出累加的值。
  • average() : 获取平均数
  • reduce{...} : 从集合中的第一项到最后一项的累计操作。
  • reduceIndexed{...} : 和reduce{}作用相同,只是其可以操作元素的下标(index)
  • reduceRight{...} : 从集合中的最后一项到第一项的累计操作。
  • reduceRightIndexed{...} : 和reduceRight{}作用相同,只是其可以操作元素的下标(index)
  • fold{...} : 和reduce{}类似,但是fold{}有一个初始值
  • foldIndexed{...} : 和reduceIndexed{}类似,但是foldIndexed{}有一个初始值
  • foldRight{...} : 和reduceRight{}类似,但是foldRight{}有一个初始值
  • foldRightIndexed{...} : 和reduceRightIndexed{}类似,但是foldRightIndexed{}有一个初始值

.

来个例子

fun test() {
    val list1 = listOf(1,2,3,4,5)

    println("  ------   any -------")
    println(list1.any())
    println(list1.any{it > 10})

    println("  ------   all -------")
    println(list1.all { it > 2 })

    println("  ------   none -------")
    println(list1.none())
    println(list1.none{ it > 2})

    println("  ------   max -------")
    println(list1.maxOrNull())
    println(list1.maxByOrNull { it + 2 })

    println("  ------   min -------")
    println(list1.minOrNull())        // 返回集合中最小的元素
    println(list1.minByOrNull { it + 2 })

    println("  ------   sum -------")
    println(list1.sum())
    println(list1.sumOf { it + 2 })


    println(" ------  average -----")
    println(list1.average())

    println("  ------   reduce  -------")
    println(list1.reduce { result, next -> result  + next})
    println(list1.reduceIndexed { index, result, next ->
        index + result + next
    })
    println(list1.reduceRight { result, next -> result  + next })
    println(list1.reduceRightIndexed {index, result, next ->
        index + result + next
    })

    println("  ------   fold  -------")
    println(list1.fold(3){result, next -> result  + next})
    println(list1.foldIndexed(3){index,result, next ->
        index + result  + next
    })
    println(list1.foldRight(3){result, next -> result  + next})
    println(list1.foldRightIndexed(3){index,result, next ->
        index + result  + next
    })
}

fun main(args: Array<String>) {
    test()
}
复制代码

.
输出:

  ------   any -------
true
false
  ------   all -------
false
  ------   none -------
false
false
  ------   max -------
5
5
  ------   min -------
1
1
  ------   sum -------
15
25
 ------  average -----
3.0
  ------   reduce  -------
15
25
15
21
  ------   fold  -------
18
28
18
28
复制代码

.
.

三.2、序列 Sequences

一句话总结:Sequences 创建之后,基本 Collections 一样,语法上几乎跟Collections一致。如果涉及到海量数据的运算,Sequences 优势超级明显,如果数据量不大, Collections 还不如 Collections。


Kotlin 中 Collections(集合)是非常好用的数据结构,同时 Kotlin 的标准库中也实现了非常多的扩展方法,帮助我们更好的使用 Collections。你也许发现了还有另一种数据结构 Sequences (序列),它同样也可以实现和 Collections 类似的功能

最大的不同在于:在每一次数据转化时,Collections 是立即执行的,而 Sequences 是延期执行的。

Sequence和Iterable对比

不同点在于, 对于集合的多步操作, Sequences提供了不同的做法.

  • 多步处理时, 对于Iterable, 执行是急切的(eagerly), 每一个步骤都完成和返回一个中间集合(collection), 后续的操作再在这个集合上继续执行.

  • sequences的多步处理是延迟的(lazily), 只有在整个链条的结果被请求的时候才会真正执行每一步的计算.

  • Iterable会对集合的所有元素先完成第一个操作, 然后整体再往下走, 对所有元素进行下一个操作.

  • Sequence则按照元素, 每个元素完成一系列操作, 接着是下一个元素.

举个例子,双层循环嵌套, Iterable的外层遍历操作符, 内层遍历元素; 而Sequence的外层遍历元素, 内层遍历操作符.


Sequence 秀一把肌肉

Collections 对比 Sequence

说了这么多,来看看Sequences在大量计算里面,是多么牛逼吧

  • 一个大量级的计算,使用 Collections

此代码运行需谨慎,性能差一点的电脑可能要等一会

//不使用 Sequences 序列,使用普通的集合操作
fun computeRunTime(action: (() -> Unit)?) {
    val startTime = System.currentTimeMillis()
    action?.invoke()
    println("the code run time is ${System.currentTimeMillis() - startTime}")
}

fun main(args: Array<String>) = computeRunTime {
    (0..100000000) // 亿
        .map { it + 1 }
        .filter { it % 2 == 0 }
        .count { it < 10 }
        .run {
            println("by using list way, result is : $this")
        }
}
复制代码

输出:

by using list way, result is : 4
the code run time is 45407
复制代码

.
.
对比一下

  • 使用 Sequence
//转成 Sequence 试试
fun computeRunTime(action: (() -> Unit)?) {
    val startTime = System.currentTimeMillis()
    action?.invoke()
    println("the code run time is ${System.currentTimeMillis() - startTime}")
}

fun main(args: Array<String>) = computeRunTime {
    (0..100000000)
        .asSequence()
        .map { it + 1 }
        .filter { it % 2 == 0 }
        .count { it < 10 }
        .run {
            println("by using list way, result is : $this")
        }
}
复制代码

输出:

by using list way, result is : 4
the code run time is 318
复制代码

45407 对比 318 这个差距够明显吧,我们现在测试的数据是1个亿,如果是千万差别没这么大。

Sequence为什么这么牛?

序列操作又被称之为惰性集合操作,Sequences 序列接口强大在于其操作的实现方式。序列中的元素求值都是惰性的,所以可以更加高效使用序列来对数据集中的元素进行链式操作 (映射、过滤、变换等)。

而不需要像普通集合(Collections)那样,每进行一次数据操作,都必须要开辟新的内存来存储中间结果,而实际上绝大多数的数据集合操作的需求关注点在于最后的结果而不是中间的过程,

Sequence跟Java的Stream有点类似

注意一点,大量数据 Sequence 是有优势的,但是如果数据量小,那么 Sequence 反而不如 Collections。

.
.

怎么创建 Sequences

  • 1、使用 Iterable 的扩展函数 asSequence 来创建
//定义声明
public fun <T> Iterable<T>.asSequence(): Sequence<T> {
    return Sequence { this.iterator() }
}
//调用实现
list.asSequence()

复制代码

.
.

  • 2、使用 generateSequence 函数生成一个序列
//定义声明
@kotlin.internal.LowPriorityInOverloadResolution
public fun <T : Any> generateSequence(seed: T?, nextFunction: (T) -> T?): Sequence<T> =
    if (seed == null)
        EmptySequence
    else
        GeneratorSequence({ seed }, nextFunction)

//调用实现,seed是序列的起始值,nextFunction迭代函数操作
val naturalNumbers = generateSequence(0) { it + 1 } //使用迭代器生成一个自然数序列

复制代码

.
.

  • 3、使用序列 (Sequence) 的扩展函数 constrainOnce 生成一次性使用的序列。
//定义声明
public fun <T> Sequence<T>.constrainOnce(): Sequence<T> {
    // as? does not work in js
    //return this as? ConstrainedOnceSequence<T> ?: ConstrainedOnceSequence(this)
    return if (this is ConstrainedOnceSequence<T>) this else ConstrainedOnceSequence(this)
}
//调用实现
val naturalNumbers = generateSequence(0) { it + 1 }
val naturalNumbersOnce = naturalNumbers.constrainOnce()

复制代码

注意:只能迭代一次,如果超出一次则会抛出 IllegalStateException (“This sequence can be consumed only once.”) 异常。

Collections 和 Sequences 如何选择

很简单,根据数据大小来选择,如果是轻量级的数据,则使用 Collections,如果是大量级的数据,那么就使用 Sequences。

Sequences原理

1、equences 是延期执行的。

2、基本原理描述: 序列操作 :基本原理是惰性求值,也就是说在进行中间操作的时候,是不会产生中间数据结果的,只有等到进行末端操作的时候才会进行求值。也就是上述例子中 0~10 中的每个数据元素都是先执行 map 操作,接着马上执行 filter 操作。然后下一个元素也是先执行 map 操作,接着马上执行 filter 操作。然而普通集合是所有元素都完执行 map 后的数据存起来,然后从存储数据集中又所有的元素执行 filter 操作存起来的原理。

四、SAM转换

Java8之后,我们将只有单一方法的接口称为SAM(Single Abstract Method)接口,Java8通过Lambda可以大大简化对于SAM接口的调用。

Kotlin当中,也有SAM。

像 OnClickListener 接口这种只有一个抽象方法接口,在kotlin中被当作函数式接口,或者SAM接口。SAM代表单抽象方法,类似的还有像RunnableCallable这样的函数式接口.

  • 函数式接口可以有多个非抽象成员,但只能有一个抽象成员。
  • 对于函数式接口,可以通过 lambda 表达式实现 SAM 转换,从而使代码更简洁、更有可读性。

举个例子:

kotlin的OnClickListener就是一个SAM接口

btn_test.setOnClickListener { view : View - 
      Toast.makeText(this,"Hello World",Toast.LENGTH_LONG).show()
}
复制代码

而在java中是这样的


Button.setOnClickListener(new OnClickListener()){
  @Override
  public void onClick(View v){
    Toast.makeText(this,"Hello World",Toast.LENGTH_LONG).show()
  }
}
复制代码

声明一个 SAM接口/函数式接口

可以用 fun 修饰符在 Kotlin 中声明一个函数式接口。

fun interface KRunnable {
   fun invoke()
}
复制代码

我们平时声明接口是 interface 接口名 ,不带fun

SAM和Lambda

一个例子

不用SAM,不用Lambda 和 使用SAM、Lambda 的对比


//定义一个接口
fun interface IntPredicate {
   fun accept(i: Int): Boolean
}
//不使用sam,正常使用
// 创建一个类的实例
val isEven = object : IntPredicate {
   override fun accept(i: Int): Boolean {
       return i % 2 == 0
   }
}
//通过利用 Kotlin 的 SAM 转换,可以改为以下等效代码:
// 通过 lambda 表达式创建一个实例
val isEven = IntPredicate { it % 2 == 0 }


复制代码

SAM构造方法:显式地把lambda转换成函数式接口.

在有的方法中需要返回一个函数式接口,不能返回一个lambda, 可以用SAM构造方法把它包装起来. 如下

fun createAllDoneRunable() : Runnable{
    return Runnable{ println(“All done”) }
}
复制代码

把Lambda生成的函数式接口放在一个变量中

除了返回值通过lambda创建函数式接口外,也可以把lambda生成的函数式接口放在一个变量中,如下

val listener = OnClickListener{
    view -> val text = when(view.id){
        R.id.button1 -> “First button”
        R.id.button2 -> “Second button”
        else -> “Unknown button"
    }
    toast(text)
}
button1.setOnClickListener(listener)
button2.setOnClickListener(listener)
复制代码

五、常用的高阶函数

  • let
  • also
  • use
  • run
  • apply

let

let

  • 当需要定义一个变量在一个特定的作用域时,可以考虑使用 let 函数。当然,更多的是用于避免 Null 判断。

  • 在 let 函数内部,用 it 指代调用 let 函数的对象,并且最后返回最后计算值

一般结构

any.let {
    // 用 it 指代 any 对象
    // todo() 是 any 对象的共有属性或方法
    // it.todo() 的返回值作为 let 函数的返回值返回
    it.todo() 
}

// 另一种用法
any?.let {
    it.todo() // any 不为 null 时才会调用 let 函数
}
复制代码

具体使用

fun main() {
  val result = "Test".let {
    println(it) // Test
    3 * 4 // result = 12
  }
  println(result) // 12
}
复制代码

安卓中的使用

对应到实际使用场景一般是 需要对一个可能为 null 的对象多次做空判断:

// 没使用let
textView?.text = "TextSetInTextView"
textView?.setTextColor(ContextCompat.getColor(this, R.color.colorAccent))
textView?.textSize = 18f


// 使用 let 函数优化后:
textView?.let { 
    it.text = "TextSetInTextView"
    it.setTextColor(ContextCompat.getColor(this, R.color.colorAccent))
    it.textSize = 18f
}
复制代码

with

  • 和 let 类似,又和 let 不同,with 最后也包含一段函数块,也是将最后的计算的结果返回。

  • with 不是以拓展的形式存在的。其将某个对象作为函数的参数,并且以 this 指代

首先来看 with 的一般结构:

whith(any) {
  // todo() 是 any 对象的共有属性或方法
  // todo() 的返回值作为 with 函数的返回值返回
  todo() 
}
复制代码

.

具体使用

class Person(val name: String, val age: Int)

fun main() {
    val chengww = Person("chengww", 18)
    val result = with(chengww) {
        println("Greetings. My name is $name, I am $age years old.")
        3 * 4 // result = 12
    }
    println(result)
}
复制代码

在 let 函数的实际使用中,我们对 textView 进行空判断,但是每次函数调用的时候还是要使用 it 对象去调用。

如果我们使用 with 函数的话,由于代码块中传入的是 this,而不是 it,那么我们就可以直接写出函数名(属性)来进行相应的设置:

安卓中的使用

if (textView == null) return
with(textView) {
    text = "TextSetInTextView"
    setTextColor(ContextCompat.getColor(this@TestActivity, R.color.colorAccent))
    textSize = 18f
}
复制代码

这段代码唯一的缺点就是要事先判空了,有没有既能像 let 那样能优雅的判空,又能写出这样的便利的代码呢?有的,我们接着来

run

run 函数基本是 let 和 with 的结合体,对象调用 run 函数,接收一个 lambda 函数为参数,传入 this 并以闭包形式返回,返回值是最后的计算结果

一般结构

any.run {
  // todo() 是 any 对象的共有属性或方法
  // todo() 的返回值作为 run 函数的返回值返回
  todo() 
}
复制代码

.
那么上面 TextView 设置各种属性的优化写法就是这样的

textView?.run {
    text = "TextSetInTextView"
    setTextColor(ContextCompat.getColor(this@TestActivity, R.color.colorAccent))
    textSize = 18f
}
复制代码

apply

apply 函数和 run 函数很像,但是 apply 最后返回的是调用对象自身。

一般结构

val result = any.apply {
  // todo() 是 any 对象的共有属性或方法
  todo() 
  3 * 4 // 最后返回的是 any 对象,而不是 12
}

println(result) // 打印的是 any 对象
复制代码

由于 apply 函数返回的是调用对象自身,我们可以借助 apply 函数的特性进行多级判空。

also

和 let 函数类似,唯一的区别就是 also 函数的返回值是调用对象本身

val result = any.also {
    // 用 it 指代 any 对象
    // todo() 是 any 对象的共有属性或方法
    it.todo() 
    3 * 4 // 将返回 any 对象,而不是 12
}
复制代码

小结

函数名 实现
let public inline fun <T, R> T.let(block: (T) -> R): R = block(this)
with public inline fun <T, R> with(receiver: T, block: T.() -> R): R = receiver.block()
run public inline fun <T, R> T.run(block: T.() -> R): R = block()
apply public inline fun T.apply(block: T.() -> Unit): T { block(); return this }
also public inline fun T.also(block: (T) -> Unit): T { block(this); return this }

image.png


参考:

www.cnblogs.com/Jetictors/p…
juejin.cn/post/707109…
www.yiibai.com/kotlin/kotl…
juejin.cn/editor/draf…
juejin.cn/post/684490…
www.jianshu.com/p/298f77dc3…

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享