<本文学习郭神《第三行代码》总结>

定义用法

高阶函数:如果一个函数接收另一个函数作为参数,或者返回值的类型是另一个函数,那么该函数称为高阶函数。

语法规则:(String, Int)-> Unit
1、在->左边的部分就是用来声明该函数接收什么参数,多个参数之间用逗号隔开,如果不接收任何参数,则用空括号,比如: ()-> Unit。
2、在右边则声明该函数返回的值类型,如果没有返回值就使用Unit,它相当于void。

比如,将上述函数类型添加到某个函数的参数声明或者返回值声明上,那么这个函数就是一个高阶函数了:

fun example(func: (String, Int) -> Unit){
func("aaa", 123)
}

在这里,example函数接收了一个函数类型的参数,因此example就是一个高阶函数。

高阶函数允许让函数类型的参数来决定函数的执行逻辑。即使是同一个函数参数,那么它的执行逻辑和最终的返回结果都可能是完全不同的。

例如:
定义一个方法num1AndNum2()的高阶函数,并让它接收两个整型和一个函数类型参数,并最终返回运算结果。

fun num1AndNum2(num1: Int, num2: Int, operation: (Int, Int) -> Int): Int{val result = operation(num1, num2)return result
}

num1AndNum2()函数的第三个参数是一个接收两个整型参数,并有一个返回值的函数参数,这里还需要定义两个方法和上述函数参数类型匹配。

fun plus(num1: Int, num2: Int) : Int{return num1 + num2
}
fun minus(num1: Int, num2: Int) : Int{return num1 - num2
}

这两个函数的参数返回类型和num1AndNum2()的函数参数类型返回完全一样。
接下来开始使用这个方法:

fun main(){val num1 = 100val num2 = 10val result1 = num1AndNum2(num1, num2, :: plus)val result2 = num1AndNum2(num1, num2, :: minus)print("result1 is $result1")print("result2 is $result2")
}

这里第三个参数使用了 :: plus、:: minus这种写法,这是一种函数的引用方法,表示将plus和minus函数作为参数传递给num1AndNum2()函数。

使用这种函数引用的写法虽然能够正常工作,但是每次调用时都还需先定义与之匹配的方法,会很繁琐,所有,这里可以替换成Lambda表达式的方法实现。

上述代码就可以修改为:

fun main(){val num1 = 100val num2 = 10val result1 = num1AndNum2(num1, num2){n1, n2 -> n1 + n2}val result2 = num1AndNum2(num1, num2){n1, n2 -> n1 - n2}

Lambda表达式提供一个指定的上下文,当需要连续调用同一个对象的多个方法时,apply函数就可以让代码更精简,比如StringBuilder。
例如:

fun StringBuilder.build(block: StringBuilder.() -> Unit): StringBuilder {block()return this
}

这里给StringBuilder定义一个build扩展函数,这个扩展函数接收一个函数参数,并且返回一个StringBuilder类型值。在函数前面加上ClassName. 就是表示这个函数类型是定义在哪个类中。

val list  = listOf("a", "b", "c")
val result = StringBuilder().build {for (s in list){append(s)}
}
print(s)

这里build函数的用法和apply用法一样,唯一区别就是build函数只是作用在StringBuilder上,而apply函数则是作用在所有类上,这就需要借助Kotlin泛型才行。

原理

现在知道高阶函数怎么用了,但是我们还需要知道它的原理。
还是用上述代码为例,调用num1AndNum2()函数,通过Lambda表达式传入两个整型参数,将代码转换成Java代码则是:

public static int num1AndNum2(int num1, int num2, Function operation){int result = (int)operation.invoke(num1, num2);return result;
}
public static void main(){int num1 = 100;int num2 = 10;int result = num1AndNum2(num1, num2, new Function() {@Overridepublic int invoke(Integer n1, Integer n2) {return n1 + n2;}});
}

在这里num1AndNum2()函数的第三个参数变成了Function接口,这是Kotlin的内置接口,里面待实现invoke()函数,在调用num1AndNum2()函数时,之前的Lambda表达式在这里变成了Function接口的匿名实现类。

所以,每调用一次Lambda表达式,都会创建一个新的匿名类实例,这也会造成额外的内存和性能开销。

inline

为了解决这个问题,Kotlin提供了内联函数,内联函数的用法非常简单,只需要在定义高阶函数时加上inline关键字即可,上述代码就可修改为

inline fun num1AndNum2(num1: Int, num2: Int, operation: (Int, Int) -> Int): Int{val result = operation(num1, num2)return result
}

首先,Kotlin编译器会将Lambda表达式中的代码替换到函数类型参数调用的地方。
然后,再将内联函数中的全部代码替换到函数调用的地方。
最终,就会替换成两个Int直接相加。

noinline

当一个高阶函数接收了多个函数参数类型时,inline会自动将所有Lambda表达式全部进行内联,如果只是想内联期中一个,inline就不满足,所以这里需要用到noinline关键字。
比如:

inline fun inlineTest(block1: () -> Unit, noinline block2: () -> Unit){}

这里使用了inline声明inlineTest函数,原本block1、block2都会被内联,但是在block2前加关键字noinline,那么只会对block1内联。

inline与oninline区别:

关键字inline:内联的函数参数类型在编译的时候回呗进行代码替换,因此没有真正的参数属性,它所引用的表达式中可以使用return关键字进行函数返回。
关键字noinline:非内联函数参数类型可以自由传递给其他任何函数,因为它就是一个真实的参数,而内联的函数参数只允许传递给另外一个内联函数,它只能进行局部返回。
比如:

fun printString(str: String, block: (String) -> Unit){block(str)
}
fun main(){val str = ""printString(str){s -> if (s.isEmpty())return@printStringprint(s)print("END")}
}

这里定义一个printString的高阶函数,用于在Lambda表达式中传入打印的字符串,如果字符串参数为空,则不打印。

在Lambda中不能直接使用return关键之,所以这里return@printString表示局部返回,并且不执行后面的代码,功能与Java中return一样。

如果传入的参数是一个空字符串,则不会执行return之后的语句。

但是如果将printString函数声明成一个内联函数,则可以再Lambda中使用return关键字。
比如:

inline fun printString(str: String, block: (String) -> Unit){block(str)
}
fun main(){val str = ""printString(str){s -> if (s.isEmpty())returnprint(s)print("END")}
}

这里return代表的是返回层的调用函数,也就是main函数。

crossinline

绝大多数高阶函数可以声明内联函数,少部分是不行的。
比如:

inline fun runRunnable(block: () ->vUnit){val runnable = Runnable{block()}runnable.run()
}

上述代码再没有inline声明是可以正常工作的,但是加上inline后会提示错误。

在runRunable函数中创建一个Runable对象,并在Runable的Lambda表达式中调用了传入的函数参数。

而Lambda表达式在编译的时候会被转换成匿名类的实现方式,实际上上述代码实在匿名类中调用了传入的函数参数。

而内联函数所引用的Lambda允许使用return进行函数返回,但是由于实在匿名类中调用的函数参数,所以不可能进行外层调用函数的返回,最多只能对匿名类中的函数调用进行返回。

也就是说,在高阶函数中创建Lambda或者匿名类的实现,并且在这些实现中调用函数参数,此时再将高阶函数声明成内联函数,一定会报错。

在这种情况下就必须使用crossinline关键字。
上述代码就可修改为:

inline fun runRunnable(crossinline block: () ->vUnit){val runnable = Runnable{block()}runnable.run()
}

这样就可以正常编译了。

因为内联函数的Lambda表达式可以使用return,但是高阶函数的匿名类中不允许使用return,这就会导致冲突,而crossinline就可以解决这种冲突。
在声明了crossinline后,就无法调用runRunable函数时的Lambda表达式中使用return进行函数返回了,但是仍然可以使用return@runRunable的方式进行局部返回。

Kotlin学习路(七):高阶函数与内联函数关系相关推荐

  1. Kotlin学习三:高阶函数

    目录 一.高阶函数的基本概念 二.常见高阶函数 1.关于list映射 2.flatMap 3.综合1 4.综合2 三.尾递归优化 四.闭包 五.函数复合 六.科理化 七.偏函数 八.小案例 一.高阶函 ...

  2. Kotlin系列四:标准函数、扩展函数、高阶函数、内联函数

    目录 一 标准函数 1.1 作用域函数 1.1.1 let 1.1.2  with 1.1.3 run 1.1.4 apply 1.1.5 also 1.1.6 takeIf 与 takeUnless ...

  3. Kotlin小知识之高阶函数

    文章目录 高阶函数 定义高阶函数 函数类型 高阶函数示例 内联函数 内联函数的作用 内联函数的用法 noinline与crossinline 高阶函数 定义高阶函数 高阶函数和Lambda的关系是密不 ...

  4. Kotlin学习(七):函数

    Kotlin学习(七):函数 函数基本用法 Kotlin 函数必须用 fun 关键字开头,后面紧跟着函数名,以及一对小括号,小括号中是函数参数列表,如果函数有返回值,在小括号后面加冒号 (:),冒号后 ...

  5. python学习——函数式编程——高阶函数

    python学习--函数式编程--高阶函数 函数式编程(高阶函数):1:map && reduce; 2 : filter; 3: sorted; ------------------ ...

  6. python内置高阶函数求导_Python——函数式编程、高阶函数和内置函数,及

    Python--函数式编程.高阶函数及内置函数 函数式编程 一.不可变数据:不用变量保存状态不修改变量 二.第一类对象:函数即"变量" 1.函数名可以当做参数传递 2.返回值可以是 ...

  7. [译]Effective Kotlin系列之探索高阶函数中inline修饰符(三)

    简述: 不知道是否有小伙伴还记得我们之前的Effective Kotlin翻译系列,之前一直忙于赶时髦研究Kotlin 1.3中的新特性.把此系列耽搁了,赶完时髦了还是得踏实探究本质和基础,从今天开始 ...

  8. Kotlin 编程核心基石—高阶函数

    前言 1. 高阶函数有多重要? 高阶函数,在 Kotlin 里有着举足轻重的地位.它是 Kotlin 函数式编程的基石,它是各种框架的关键元素,比如:协程,Jetpack Compose,Gradle ...

  9. python学习总结1—高阶函数

    python 高阶函数学习 高阶函数 介绍python 高阶函数的使用方法 map/reduce函数 map函数 利用map()函数,把用户输入的不规范的英文名字,变为首字母大写,其他小写的规范名字. ...

最新文章

  1. kotlin + springboot 整合redis,Redis工具类编写及单元测试
  2. HDU 2084 数塔 DP
  3. Windows PE变形练手1-用PE自己的机器码修改自己的逻辑
  4. 理性预期学派(Rational Expectation School)
  5. java HashMap问题
  6. mysql中文乱码 go_Mysql binlog乱码问题研究-Go语言中文社区
  7. 你真的懂软件测试人员的痛苦吗?——目前软件测试5大误区
  8. Android -- 写xml到SD卡中
  9. socket调试工具、socket调试软件、tcp调试工具、tcp调试软件(sokit)
  10. c语言校时程序,我校C语言程序设计教与学的思考
  11. excel能和html链接吗,excel中怎么设置超链接并且整个excel表格发给别人时超链接还是能用...
  12. 走向全民开发,低代码重塑企业数字化生产力 | 爱分析报告
  13. 【案例】某区医院绩效工资分配系统和绩效工资分配优化服务案例
  14. python基础之logging模块
  15. 当前时间的七天前和七天后
  16. MATLAB绘制小胖墩
  17. OrangePi 5 Docker下安装OpenWRT作软路由(同样适用于树莓派等设备)
  18. ffmpeg 分辨率 压缩_视频怎么在尽量不损害画质的前提下压缩?
  19. php求价格最低,php-将Woo-commerce变体销售价格调至低于实际价格
  20. CNNs中,什么是max pooling, 为什么需要max pooling

热门文章

  1. 2017国庆 济南清北学堂 8天乐
  2. c#机器人聊天软件_C#winForm 聊天只能机器人(完整版)
  3. wtc java 代码 tpcall(servicename_通过wtc使tuxedo与weblogic通信开发
  4. 4.目前住院病人主要由护士护理,这样做不仅需要大量护士,而且由于不能随时观察危重病人的病情变化,还可能会延误抢救时机。
  5. android开发 问卷调查案例_安卓 问卷调查Demo 原生代码
  6. geany配置python_python使用Geany编辑器配置方法
  7. 串ababaaababaa的next和串ababaabab的nextval
  8. 通过Excel制作下拉框筛选出成绩
  9. 【程序员修炼日志】校招与社招的感悟
  10. 计算机网络:P4.3-网络层(下)