Kotlin学习(四):kotlin函数的高级应用

12 篇文章 0 订阅

系列文章路引 👀 https://blog.csdn.net/weixin_44235109/article/details/119680974

一、kotlin高阶函数

1.高阶函数的定义

普通函数的入参或者返回类型都是基本数据类型或者对象,而高阶函数参数的类型包含一个函数或者说返回一个函数类型
示例如下所示:

//传入的参数是函数
fun needsFunction(block:() -> Unit){
	block()
}
//返回的类型是函数类型
fun returnsFunction():()->String{
	return {
		"hello world"
	}
}

2.几个常见的高阶函数

//接受函数作为参数,在循环迭代中调用接收的函数
inline fun IntArray.forEatch(action:(Int)->Unit):Unit{
	for(element in this) action(element)
}

//传入函数,再把函数作为参数继续传递
inline fun <R> IntArray.map(transform : (Int) -> R):List<R>{
	return mapTo(ArrayList<R>(size),transform)
}

3.高阶函数的调用

比如调用IntArray forEatch 的方法进行打印,我们可以写成如下的两种方式

	//直接引用对应的函数作为参数传入
    intArrayOf(1,2,3).forEach (::print)
    //使用lambda表达式的方式写入
    intArrayOf(1,2,3).forEach {
        print(it)
    }
  • 直接引用对应的函数作为参数传入,从上面的分析我们知道forEach()方法需要传入一个类型为(Int)->Unit 的函数,所以直接可以引用 一个该类型的函数 比如 print() 。 print的类型(Int)->Unit(说明:print有很多的重载(Int)-> Unit只是其中一个重载)
  • 可以使用lambda表达式,如果括号什么都不写,括号可以进行省略;即只有一个lambda作为参数的话,可以省略小括号;且只有一个参数的表达式,参数的形参可以使用it进行表示

4.Demo-计算函数花费时间

//    计算函数执行的时间
fun cost(block: () -> Unit) {
    val start = System.currentTimeMillis()
    block()
    println("执行时间${System.currentTimeMillis() - start}ms")
}
//    斐波那契数列
fun fibonacci(): () -> Long {
    var start = 0L
    var second = 1L
//    匿名函数 lambda表达式的表现形式
    return {
        val next = start + second
        val current = start
        start = second
        second = next
        current
    }
}
//    使用
fun main() {
//    将lambda表达式移到最外面,省略小括号
    cost {
        val function = fibonacci()
        for (i in 0..10) {
            val longVal = function()
            println(longVal)
        }
    }
}

二、kotlin内联函数

1.内联函数概念

用inline关键字进行修饰,如果该函数接受一个匿名函数(lambda表达式),则可以使用inline关键字修饰改函数,然后接受的匿名函数会被编译器优化,内联到函数内部,会节省开销。
示例如下所示:

//内联函数
inline fun IntArray.forEatch(action:(Int)->Unit):Unit{
	for(element in this) action(element)
}

//调用处
fun main(){
	val ints = intArrayOf()
	ints.forEach{
		println("hello$it")
	}
}

//实际编译之后的调用(大致)
fun main(){
	val ints = intArrayOf()
	for(element in this) {
		println("hello$element")
	}
}

上述所示,使用inline修饰之后,在编译期间kotlin会对代码调用栈进行优化。

2.高阶函数和内联函数

示例如下所示:

inline fun cost(block:()->Unit){
	val start = System.currentTimeMillis()
	block()
	println(System.currentTimeMillis() - start)
}

//调用
cost{
	println("hello")
}

//实际编译效果
val start = System.currentTimeMillis()
println("hello")
println(System.currentTimeMillis() - start)

高阶函数内联

  • 函数本身被内联到调用处
  • 函数的函数参数被内联到调用处

3.内联函数的限制以及详细说明

  • public/protected的内敛方法只能访问对应类的public成员(也可以理解,毕竟可以在编译期间优化到调用处,怎么访问私有属性呢)
  • 内联函数的内联函数参数不能被储存(赋值给变量)
  • 内联函数的内联函数参数只能传递给其他内联函数参数
    另: 与inline一块配合使用的有noinline以及crossinline。关于内联函数的详细说明,以及这个三个参数的详细区别,可参考:kotlin:inline、noinline、crossinline还傻傻分不清楚?看这一篇就够了

三、kotlin几个常用的高阶函数let、run、also、apply、use

1.let

let源码如下所示:

public inline fun <T, R> T.let(block: (T) -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block(this)
}

注意:我们可以忽略掉contract契约检查(下面遇到的源码如包含contract,都可以进行忽略,且不在提及),那么核心的函数其实就是return block(this)
let : 将receiver作为参数传入block,并且返回block的返回值

简单使用实例如下所示:

val strLenght = arrayOf("hello", "world").let {
    it.joinToString(" ") { s: String ->
        "$s length: ${s.length}"
    }
}
println(strLenght)

//输出
hello length: 5 world length: 5

2.run

run源码如下所示:

public inline fun <T, R> T.run(block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block()
}

接收的block类型为T.() -> Unit 所以 block里面可使用this指代调用者
run : block里面可使用this指代调用者,并且返回block的返回值

简单使用实例如下所示:

val strLenght = arrayOf("hello", "world").run {
    this.joinToString(" ") { s: String ->
        "$s length: ${s.length}"
    }
}
println(strLenght)

//输出
hello length: 5 world length: 5

3.also

also源码如下所示:

public inline fun <T> T.also(block: (T) -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block(this)
    return this
}

和let的区别就是返回类型不一样了,block是无返回类型,整个函数返回调用者T
核心函数 block(this) return this

简单使用实例如下所示:

val arrayStr = arrayOf("hello", "world").also {
    val joinToString = it.joinToString(" ") { s: String ->
        "$s length: ${s.length}"
    }
    println(joinToString)
}
println(arrayStr is Array)

//输出
hello length: 5 world length: 5
true

4.apply

apply源码如下所示:

public inline fun <T> T.apply(block: T.() -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block()
    return this
}

和run的区别就是返回类型不一样了,block是无返回类型,整个函数返回调用者T
核心函数 block() return this

简单使用实例如下所示:

val arrayStr = arrayOf("hello", "world").apply{
    val joinToString = it.joinToString(" ") { s: String ->
        "$s length: ${s.length}"
    }
    println(joinToString)
}
println(arrayStr is Array)

//输出
hello length: 5 world length: 5
true

5.use

use源码如下所示:

public inline fun <T : Closeable?, R> T.use(block: (T) -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    var exception: Throwable? = null
    try {
        return block(this)
    } catch (e: Throwable) {
        exception = e
        throw e
    } finally {
        when {
            apiVersionIsAtLeast(1, 1, 0) -> this.closeFinally(exception)
            this == null -> {}
            exception == null -> close()
            else ->
                try {
                    close()
                } catch (closeException: Throwable) {
                    // cause.addSuppressed(closeException) // ignored here
                }
        }
    }
}

核心代码就是try catch那一块,很明显,使用use函数自动帮我们处理了,异常以及资源的关闭。所以使用Closeable相关操作的时候,推荐使用use来进行资源管理
简单使用实例如下所示:

val datas = LinkedList<Pair<Char, Int>>()
File("build.gradle.kts").inputStream().reader().buffered().use {
    var strLine = it.readLine()
    while (strLine != "") {
        strLine.groupBy { char ->
            char
        }.map { map ->
            map.key to map.value.size
        }.run {
            datas.addAll(this)
        }
        strLine = it.readLine()
    }
}
println(datas.joinToString())

//输出
(p, 1), (l, 1), (u, 1), (g, 1), (i, 1), (n, 1), (s, 1), ( , 1), ({, 1), (/, 2), ( , 5), (i, 1), (d, 1), (', 2), (j, 1), (a, 2), (v, 1), (/, 2), ( , 7), (i, 4), (d, 1), (', 4), (o, 3), (r, 3), (g, 1), (., 5), (j, 2), (e, 2), (t, 2), ...展示内容有限,就不全部粘贴过来了

四、kotlin集合序列变换forEach、filter、map、flatmap、sum、reduce、fold

1.forEach函数

forEach,熟悉java的朋友相信都不陌生,且本系列文章在之前也多次用到forEach,下面我们就看看forEach具体是怎么操作的吧
forEach源码如下所示:

public inline fun <T> Array<out T>.forEach(action: (T) -> Unit): Unit {
    for (element in this) action(element)
}

很简单是不是,就是对集合元素进行了循环处理,调用了传进来的block
那按照惯例,也提供一个小demo

arrayOf(1,2,3,4).forEach {
    print(it)
}
//输出
1234

2.集合映射操作相关filter、map、flatMap

filter,保留满足条件的元素
源码如下所示:

public inline fun <T> Array<out T>.filter(predicate: (T) -> Boolean): List<T> {
    return filterTo(ArrayList<T>(), predicate)
}
public inline fun <T, C : MutableCollection<in T>> Array<out T>.filterTo(destination: C, predicate: (T) -> Boolean): C {
    for (element in this) if (predicate(element)) destination.add(element)
    return destination
}

正所谓,知其然也要知其所以然。如上源码所示,filter的本质就是,循环判断block是否满足条件,如果满足则添加进入list,最后返回
简单使用如下:

arrayOf(1,2,3,4,5,6).filter {
    it>3
}.joinToString ().let {
    println(it)
}

//输出
4, 5, 6

map,集合中所有的元素一一映射到其他的元素构成新的集合
源码如下所示:

public inline fun <T, R> Array<out T>.map(transform: (T) -> R): List<R> {
    return mapTo(ArrayList<R>(size), transform)
}
public inline fun <T, R, C : MutableCollection<in R>> Array<out T>.mapTo(destination: C, transform: (T) -> R): C {
    for (item in this)
        destination.add(transform(item))
    return destination
}

分析发现,其实本质就是创建一个新的集合,加入传入map的函数的返回值而已
简单使用如下:

arrayOf(1,2,3,4,5,6).map {
    "$it hello"
}.joinToString().let {
    println(it)
}

//输出
1 hello, 2 hello, 3 hello, 4 hello, 5 hello, 6 hello

flatMap,集合中所有的元素一一映射到新集合并合并这些集合得到新集合
源码如下所示:

public inline fun <T, R> Array<out T>.flatMap(transform: (T) -> Iterable<R>): List<R> {
    return flatMapTo(ArrayList<R>(), transform)
}
public inline fun <T, R, C : MutableCollection<in R>> Array<out T>.flatMapTo(destination: C, transform: (T) -> Iterable<R>): C {
    for (element in this) {
        val list = transform(element)
        destination.addAll(list)
    }
    return destination
}

好多同学可能都对flatMap所要表达的意思,感到困惑难以理解,但是我们仔细分析一波源码,弄懂原理就简单很多了。
首先可以看到flatMap也是创建了新的ArrayList集合并且将函数重新传入到了flatMapTo的这个函数里了,这个时候我们注意,我们传入的函数的返回类型必须是可迭代的(Iterable类型)。
那我们在看flatMapTo干了什么事情,先是循环调用者的集合,然后调用函数,拿到我们传入的函数返回的集合,最后在addAll到信创建的ArrayList返回。
这么一说是不是就好理解flatMap的映射关系了,下面我们可以看一个demo
Demo如下所示:

arrayOf(1,2,3,4,5,6).flatMap {
    val arrayList = ArrayList<Any>()
    arrayList.add(it)
    arrayList.add(1111)
    arrayList
}.joinToString().let {
    println(it)
}

//输出
1, 1111, 2, 1111, 3, 1111, 4, 1111, 5, 1111, 6, 1111

3.集合的聚合操作说明sum、reduce、fold

sum,所有元素求和
源码如下所示:

public fun Array<out Int>.sum(): Int {
    var sum: Int = 0
    for (element in this) {
        sum += element
    }
    return sum
}

较为简单,就不做说明了。
Demo所示如下:

println(arrayOf(1,2,3,4,5,6).sum())

//输出
21

reduce,将元素依次按规则聚合,结果与元素类型一致
源码如下所示:

public inline fun <S, T : S> Array<out T>.reduce(operation: (acc: S, T) -> S): S {
    if (isEmpty())
        throw UnsupportedOperationException("Empty array can't be reduced.")
    var accumulator: S = this[0]
    for (index in 1..lastIndex) {
        accumulator = operation(accumulator, this[index])
    }
    return accumulator
}

这个也比较好理解,但需要注意几点
1.外部调用的lambda表达式会循环被调用,因为reduce内部的循环
2.传入lambda的两个参数第一个acc代表的是,上一次被调用之后的计算的最新的值,T则代表当前需要的调用者集合的值。
简单使用Demo所示如下:

val value = arrayOf(1, 2, 3, 4, 5, 6).reduce { acc, i ->
   	acc + i * i
}
println(value)

//输出
91

fold,给定初始值,将元素依次按规则聚合,结果与元素类型一致
源码如下所示:

public inline fun <T, R> Array<out T>.fold(initial: R, operation: (acc: R, T) -> R): R {
    var accumulator = initial
    for (element in this) accumulator = operation(accumulator, element)
    return accumulator
}

可以看得出,fold与reduce唯一的区别就是,fold的初始值需要手动传入,而reduce的初始值拿的是集合的第一个元素
简单使用Demo所示如下:

val value = arrayOf(1, 2, 3, 4, 5, 6).fold(10) { acc, i ->
    acc + i * i
}
println(value)

//输出
101

五、kotlin与java的SAM(Sinigle Abstract Method)转换对比

1.java与kotlin转换条件对比

java:一个参数类型为只有一个方法的接口的方法调用时可用Lambda表达式做转换作为参数
kotlin:一个参数类型为只有一个方法的java接口的java方法调用时可用Lambda表达式做转换作为参数
对比例子代码如下所示:

//java正常调用
new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("thread");
            }
        }).start();
//java  sam转换使用lambda调用
new Thread(
	() -> System.out.println("thread")
).start();

//kotlin 正常调用
Thread(object :Runnable{
    override fun run() {
        println("thread")
    }
}).start()
//kotlin sam转换调用lambda
Thread { 
    println("thread") 
}.start()

java的大家都比较熟悉了,我们看一下kotlin的转换是怎么操作的吧
在这里插入图片描述
实际上相当于时编译器帮我们new了一个runable对象,并且里面调用了我们的匿名函数

2.kotlin使用SAM转换注意点

正因为编译器帮我们创建了对应的对象,所以:添加移除事件时或者涉及到同一个对象操作时,调用java的接口需要注意,直接将对应接口对象new出来,避免进行SAM转换,否则操作的就不是同一个对象!!!
在这里插入图片描述

总结

以上就是本次更新的kotlin相关的知识点了,大家都学废了吗?
最后放一个小demo,加强一下动手的能力
统计字符个数

fun main() {
    val linkedList = LinkedList<Pair<Char, Int>>()
//    使用  readerBuffer  可以一行一行读取数据
    File("build.gradle").inputStream().reader().buffered()
        .use {
            var str = it.readLine()
            while (!("" == str)) {

                str.toCharArray()
                    .groupBy { it }
                    .map {
//                        映射为pair类型
                        it.key to it.value.size
                    }.let {
                        linkedList.addAll(it)
                    }

                str = it.readLine()
            }
        }

    linkedList.forEach {
        println(it)
    }

//    也可以使用file的扩展方法将文件内容全部读取出来  isWhitespace()  判断不是空字符
    File("build.gradle").readText().toCharArray()
        .filter { !it.isWhitespace() }
        .groupBy { it }
        .map {
            it.key to it.value.size
        }.let {
            println(it)
        }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

pumpkin的玄学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值