首页 > 其他分享 >Kotlin高阶函数、内联函数以及集合变换序列

Kotlin高阶函数、内联函数以及集合变换序列

时间:2023-06-22 14:06:22浏览次数:33  
标签:函数 val Int Kotlin println 内联 block


一、高阶函数

高阶函数的一个重要特征就是参数类型包含函数,或者该函数的返回值类型是一个函数类型,那么该函数就被称为是高阶函数。

下面看看如何把函数作为参数声明到另一个函数中:

//参数包含函数类型,()代表一个匿名函数,Unit代表无返回值
fun paramFunction(block:() -> Unit){
    block()
}

返回值是是一个函数的形式

fun returnFunction():() -> Long{
    return {System.currentTimeMills()}
}

高阶函数的调用

高阶函数的一个重要特性就是可以让函数类型参数决定函数的执行逻辑,就是说相同返回类型但执行逻辑不同的函数作为参数传入时,返回结果是不一样的。下面我们定义一个函数看一下:

//定义一个所谓的高级函数 函数参数block是两个参数为Int类型的函数变量,返回值是Int
fun exampleFun(a: Int, b: Int, block: (Int, Int) -> Int): Int {
    return block(a, b)
}

//定义两个返回类型一样 但执行逻辑不一样的函数
fun aFun(n:Int,m:Int):Int{
    return m*n
}

fun bFun(n:Int,m:Int):Int{
    return m+n
}

//调用
fun main() {
    val aValue = exampleFun(2,3,::aFun)
    println("aFun作为参数的返回值:$aValue")
    val bValue = exampleFun(2,3,::bFun)
    println("bFun作为参数的返回值:$bValue")
}

//输出结果
aFun作为参数的返回:6
bFun作为参数的返回:5

这里有一点就是我们需要定义多个逻辑不同的函数,相对来说有点麻烦,但是其实我们可以看到,传的函数参数是可以简化成lamba表达式,之前我们学lamba表达式的可以知道:

val func:(Int) -> Unit = {p:Int -> println§ }

那上述的block对应的lamba表达式就是:

val block:(Int,Int) -> Int = {a:Int,b:Int -> a+b }

其中a+b就是执行逻辑,可以是a+b,a-b或者a*b,只要返回Int类型就行。

那前面的高级函数调用可以灵活写成

val aValue = exampleFun(2,3){a,b -> a+b}

二、内联函数

内联函数就是在函数前面加上 inline关键字修饰。内联函数的使用可以减少函数的调用,有性能开销的优化作用,这里高阶函数和和内联会更匹配,原因在于调用高阶函数的时候,作为函数参数的变量调用也会是一个函数的调用,这个时候就会创建一个匿名类对象代替Lamba表达式,对象创建越多,内存开销肯定会大。看看下面这段代码

fun inlineFun(block:() -> Unit){
        val curTime = System.currentTimeMillis()
        block()
        println(System.currentTimeMillis() - curTime)
    }
    
    //inlineFun的lamba表达式方式写法
    inlineFun{
        println("Kotlin")
    }
    
    //这个时候,我们只是想调用打印这个方法,看看它调用的时间,但这个时候依旧会创建lamba表达式的匿名类,
    //这样消耗的时间可能会大于打印时间
    
   //那我们用定义为内联函数后的调用是如何的
   
   inline fun inlineFun(block:() -> Unit){
        val curTime = System.currentTimeMillis()
        block()
        println(System.currentTimeMillis() - curTime)
   }
    
   //这个时候再调用
   inlineFun{
       println("Kotlin")
   }
   
   //其实际执行的是
   val curTime = System.currentTimeMillis()
   println("Kotlin")
   println(System.currentTimeMillis() - curTime)

这样编译时创建lamba表达式匿名类开销就没了,所以内联函数的原理是:Kotlin在编译时会把内联函数内代码搬到要调用的地方, 就好像我们写了这段代码一样。

内联函数的noinline和crossinline

一个高阶函数如果被inline关键字修饰,那么它接收的所有函数类型的参数均会被内联,如果想某个函数类型的参数不被内联,就需要用关键字noinline修饰。

那这就有一个问题:既然前面我们说内联函数能消除Lambda表达式运行时带来的额外内存开销,那么为啥还提供了一个noinline来排除内联呢?

这里主要是因为内联函数类型的参数只能传递给内联函数,而非内联则可以传递给任何函数,另外不同的一点是内联函数可以返回外部函数,就是调用该内联函数的函数,而非内联函数则返回局部。

下面代码看看内联函数的返回和非内联函数的返回:

inline fun nonLocalReturn(block:() -> Unit){
    block()
}

fun main(){

    println("main start")
    nonLocalReturn{
        println("lambda start")
        //这里return 就是直接返回到main函数了
        return
        println("lambda end")
    }
    println("main end")
}
//执行打印的结果是
main start
lambda start
说明直接返回到main函数了,没有再往下执行


//如果我们没有定义成内联函数
fun nonLocalReturn(block:() -> Unit){
    block()
}

fun main(){
    println("main start")
    nonLocalReturn{
        println("lambda start")
        return@nonLocalReturn
        println("lambda end")
    }
    println("main end")
}

//这里打印的结果是
main start
lambda start
main end
说明仅仅是返回了nonLocalReturn函数,main函数还是往下走了

crossinline关键字

绝大多数高阶函数都可以声明为内联函数,但是也有例外的情况。如下列代码:

inline fun Runnable(block:() -> Unit):Runnable{
    return object:Runnbale {
        override fun run(){
            block()
        }
    }
}

这个时候idea会提示block()调用错误,这是因为有可能存在不合法的non-local return,block()的调用处与定义处不在一个调用上下文。

那么如何解决这个问呢?

使用crossinline关键字修饰传递的函数就可以使它在Lambda表达式中一定不return,而且crossinline修饰除了不return之外,其他内联函数的属性都是一样的。

三、几个常用的高阶函数

let函数

let函数调用一般是针对一个定义在特定情况下使用的变量,例如我们可以避免对变量的null判断。观察如下代码:

// any 不为null 时才会调用let 函数
any?.let {
    //it 就是代表any对象
    // todo()方法就是any对象的方法
    // it.todo()的返回值作为let函数的返回值返回
    it.todo() 
}

在Android开发中的实际场景就是我们可能要对一个变量进行多次判空才调用,如:

mTextView?.text = "Kotlin"
mTextView?.textSize = 16f

使用let函数后可以简写为

mTextView?.let{
    it.text = "Kotlin"
    it.textSize = 16f
}

run函数

run函数其实就是let函数的升级版,run函数接受一个lambda 函数为参数,传入this并以闭包形式返回,返回值是最后的计算结果,那么上述的代码可以优化成下面这样:

mTextView?.run{
    text = "Kotlin"
    textSize = 16f
}

apply函数

apply函数和run函数的结构模式很相像,但是apply函数返回的是调用对象本身,因为apply函数的这个特性,所以它特别有助于我们进行多级的判null行为。下面我们通过一个代码示例了解一下:

apply函数的一般结构:

val applyRes = any.apply{
    //todo()是 any 对象的共有属性或方法
    todo()
    // 最后返回的是any对象,而不是2
    1+1
}

apply的简单应用举例:

//定义了一个公司对象 包含部门和人员名字
class Company(var department:Department? = null){
    class Department(var departmentName: String? = null,var person:Person? = null){
        class Person(var name:String? = null)
    }
}


fun main() {
    val company:Company? = Company(Company.Department("一部",  Company.Department.Person("Jeremy")))
    company?.department?.apply {
        departmentName = "二部"
    }?.person?.name.also {
        println("这个${company?.department?.departmentName}的员工是$it")
    }
}

打印的信息是:这个二部的员工是Jeremy

also函数

上述的apply应用举例中,我们最后调用了also函数,这个函数的作用和let函数是类似的,其中it就是返回的调用对象本身,其一般的结构格式就是:

val alsoRes = any.also {
    //it代表的就是any todo是any的属性方法
    it.todo() 
    1+1 //返回的是any对象,而不是2
}

use函数

use函数最大的一个特征就是会自动关闭调用者,无论其中间是否出现异常,因为use函数内部实现也是通过try-catch-finally块捕捉的方式,而close操作在finally里面执行,所以无论是正常结束还是出现异常,都能正确关闭调用者。

怎么才能调用use函数呢?

凡是实现了Closeable接口的对象都可以调用use函数。

因为use函数使得Kotlin中对File对象和IO流操作变得行云流水。

File("build.gradle").inputStream().reader().buffered()
    .use {
        print(it.readLines())

四、集合变换序列

4.1、filter操作

filter的操作是保留满足条件的元素得到新的列表,观察下列代码:

val arr = intArrayOf(1,2,3,4)
//asSequence()转换成懒序列
val arrFilter = arr.asSequence().filter { it%2 == 0 }
val arrFilter = arr.filter { it%2 == 0 }

for (e in arrFilter){
    println(e)
}

打印的数据:
2
4

4.2、map变换

map变换就是一种映射一一对应的操作:

val list: MutableList<Int> = mutableListOf(1, 2, 3, 4)
val listMap = list.map { it*2 + 1}
for (e in listMap){
    println(e)
}

打印的数据是:
3
5
7
9

集合的每个数据对应执行lamba表达式中的it*2 + 1返回一个新的数据集合

下面我们看一下filter和map结合的使用

懒序列调用法:

val list: MutableList<Int> = mutableListOf(1, 2, 3, 4)
list.asSequence().filter {
    println("filter $it")
    it % 2 ==0
}.map {
    println("map $it")
    it*2 + 1
}.forEach {
    println("forEach $it")
}

打印结果:
filter 1
filter 2
map 2
forEach 5
filter 3
filter 4
map 4
forEach 9

上述看到打印结果是主要有符合条件的都会先往下走,懒序列有一个很重要的点就是forEach是一个阀门作用,如果把forEach去掉就不会执行前面filter和map的操作,这就是懒序列的意义,当你需要的时候才会执行。那我们看一下饿汉式的调用结果:

val list: MutableList<Int> = mutableListOf(1, 2, 3, 4)
list.filter {
    println("filter $it")
    it % 2 ==0
}.map {
    println("map $it")
    it*2 + 1
}.forEach {
    println("forEach $it")
}

打印结果:
filter 1
filter 2
filter 3
filter 4
map 2
map 4
forEach 5
forEach 9

可以看到它无论是否符合条件,都会先执行完再进行下一步操作。

4.3、flatMap变换

flatMap就是把集合的元素映射成集合,每个元素对应一个新的集合,最后把这些集合组成一个最终的新的集合。如下面代码:

val list: MutableList<Int> = mutableListOf(1, 2, 3, 4)
list.flatMap { 0 until it }.joinToString().let(::println)

打印结果
0, 0, 1, 0, 1, 2, 0, 1, 2, 3
即是每个元素依次对应的集合是:
0 -> 0
1 -> 0,1
2 -> 0,1,2
3 -> 0,1,2,3

flatMap的返回值只有是Iterable即可,如下:

val list: MutableList<Int> = mutableListOf(1, 2, 3, 4)
  val listFlatMap = list.flatMap { listOf(it+1) }
  for (e in listFlatMap){
     println(e)
  }
  
 打印结果:
 2
 3
 4
 5

4.4、集合的聚合操作举例

sum:所有元素求和。

reduce:将元素依次按规则聚合,结果与元素类型一致。

fold:给定初始化值,将元素按规则聚合,结果与初始化值类型一致。

其中reduce其实就是fold的简化版,我们简单看一下fold函数的操作:

val list: MutableList<Int> = mutableListOf(1, 2, 3, 4)
val strFold = list.fold(StringBuffer()){
    //acc就是上次拼接的结果
    acc, i -> acc.append(i)
}
println(strFold)

打印结果:
1234

总结

首先是感觉自己的基础还是不够吧,大厂好像都喜欢问这些底层原理。

标签:函数,val,Int,Kotlin,println,内联,block
From: https://blog.51cto.com/u_16163453/6534762

相关文章

  • 在sql中使用函数,遇到net.sf.jsqlparser.parser.ParseException异常
    异常详情如下Causedby:net.sf.jsqlparser.parser.ParseException:Encountered""->""->""atline1,column31.Wasexpectingoneof:"AS"..."DO"..."ANY"..."KEY"...……(中间省略很多符号)atnet.......
  • Kotlin flow实践总结
    背景最近学了下KotlinFlow,顺便在项目中进行了实践,做一下总结。Flow是什么按顺序发出多个值的数据流。本质就是一个生产者消费者模型,生产者发送数据给消费者进行消费。冷流:当执行collect的时候(也就是有消费者的时候),生产者才开始发射数据流。生产者与消费者是一对一的关系。当生产......
  • Excel中PMT计算月供函数的java实现
    Excel中计算月供的公式名叫PMT,有关这个公式的详细描述如下:http://office.microsoft.com/zh-cn/excel-help/HP010342769.aspx下图是Excel中使用这个公式的一个简单说明。Java中实现这个公式可以用下面函数注意,这个函数的所有输入参数都是double类型的。包括支付的月份数,否则计算......
  • Kotlin协程:Flow基础原理
    本文分析示例代码如下:launch(Dispatchers.Main){flow{emit(1)emit(2)}.collect{delay(1000)withContext(Dispatchers.IO){Log.d("liduo","$it")}Log.d("liduo",&......
  • Kotlin 增量编译是怎么实现的?
    前言编译运行是一个Android开发者每天都要做的工作,增量编译对于开发者也极其重要,高命中率的增量编译可以极大的提高开发者的开发效率与体验之前写了一些文章介绍Kotlin增量编译的原理,以及Kotlin1.7支持了跨模块增量编译了解了这些基本原理之后,我们今天一起来看下Kotlin增量编译......
  • 换个姿势,十分钟拿下Java/Kotlin泛型
    0x1、引言解完BUG,又有时间摸鱼学点东西了,最近在复习Kotlin,跟着朱涛的《Kotlin编程第一课》查缺补漏。看到泛型这一章时,想起之前面一家小公司时的面试题:说下你对泛型协变和逆变的理解?读者可以试试在不查资料的情况下能否答得上来?反正我当时是没想起来,尽管写过一篇《Kotlin刨根问底......
  • Power BI - HASONEVALUE函数
    返回值:是布尔值,True,False如果要判断的内容有多个值,不会报错,而是返回False。  ......
  • delphi 字符串比较函数
    字符串比较函数列表方法说明大小写System.SysUtils.TStringHelper.StartsWith返回是否以给定的字符串开头。区分大小写System.SysUtils.TStringHelper.StartsText返回是否以给定的字符串开头。不区分大小写System.SysUtils.TStringHelper.EndsWith返回是否......
  • MySQL 视图&存储过程&函数
    1视图1.1视图的作用当我们创建一张表的视图后,可以用和表差不多的使用方式来使用视图,比如可以对视图进行select查询操作、过滤或者排序数据等等。同时,也可以联结其它视图或者表,甚至可以添加和更新数据(但一般不会这么做,而且存在诸多限制)。总结起来,视图有以下优点:重用SQL语句,简......
  • 一元函数积分学
    目录一元函数积分学原函数与不定积分的概念导数与不定积分的关系牛顿-莱布尼茨公式基本积分表求不定积分直接积分法凑微分法换元积分法分部积分法定积分定积分的计算利用可加性利用积分限利用对称性利用几何意义定积分的几何应用平面图形的面积旋转体的体积平面曲线的弧长积分与导......