kotlin 之函数进阶
文章目录
- 高阶函数
- 内联函数
- 几个有用的函数
- 集合变换与序列
- 集合的聚合操作
- SAM 转换
- 案例
高阶函数
参数类型包含了函数,或者返回类型为函数
fun needsFunction(block: () -> Unit) {//调用函数block()
}fun returnsFunction(): () -> Long {//返回一个函数return { System.currentTimeMillis() }
}
内联函数
当使用 Lambad 表达式时,他会被编译为一个匿名类。这表示每调用一次 lambad ,就会有一个额外的类被创建,这会带来运行的额外开销,导致 lambad 比使用一个直接执行相同代码的函数效率更低。
如果使用 inline 标记一个函数,这个函数被调用的时候编译器并不会生成函数的调用代码。而是 使用函数实现的真实代码替换每一次函数的调用
如下:
fun main() {cost {println("Hello")}
}// 添加 inline 即为内联函数
inline fun cost(block: () -> Unit) {val start = System.currentTimeMillis()block()println(System.currentTimeMillis() - start)
}
反编译后如下
public static final void main() {int $i$f$cost = false;long start$iv = System.currentTimeMillis();int var3 = false;String var4 = "Hello";boolean var5 = false;System.out.println(var4);long var6 = System.currentTimeMillis() - start$iv;var5 = false;System.out.println(var6);}
Lambad 表达式和 cost 的实现部分都被内联了。看起来是调用了 cost ,实际上是函数本身被内联到了调用处。
内联:
1,函数本身被内联到调用处
2,函数的函数参数被内联到调用处
内联函数的 return
val list = listOf<Int>(1, 2, 3, 4)list.forEach {if (it == 3) {//类似于 continue,退出本次return@forEach}println(it)}
non-local return
fun main() {//内联函数cost {println("Hello")return}println("not fond!")
}inline fun cost(block: () -> Unit) {val start = System.currentTimeMillis()block()println(System.currentTimeMillis() - start)
}
//Hello
如果在 lambad 中使用 return 关键字,他会从调用处返回,而并非是lambda 中返回。因为这是一个内联函数,表达式中的内容相当于被提取到了调用的地方进行执行,所以就会在 main 中直接 return。
从 lambda 中返回:
fun main() {//内联函数cost label@{println("Hello")return@label}println("not fond!")
}
//Hello
//1
//not fond!
增加 label 标签后,就可以局部返回。
//定义 内联函数 添加 inline 即为内联函数
inline fun cost(crossinline block: () -> Unit): Runnable {return object : Runnable {override fun run() {block() //报错,因为 block 的调用处与定义处不在同一个调用上下文//添加 crossinline 关键字即可解决 ,禁止 non-local return//添加 noinlin 也可以解决,只不过 内联函数就没有意义了。}}
}
内联属性
var pocket: Double = 0.0
var money: Doubleinline get() {return pocket}inline set(value) {pocket = value}
和 内联函数一样,在调用 money 的时候会执行在调用的地方
内联函数的限制
1,public /protected 的内联方法只能访问对应类的 public 成员
2,内联函数的内联函数参数不能存储(赋值给变量)
3,内联函数的内联函数参数只能传递给其他内联函数参数
几个有用的函数
函数名 | 介绍 | 推荐指数 |
---|---|---|
let | val r = X.let{x -> R} | ★★★ |
run | val r = X.run{this.X ->R} | ★ |
also | val x = X.also{x -> Unit} | ★★★ |
apply | val x = X.apply{this:X -> Unit} | ★ |
use | val r = Closeable.use{c -> R} | ★★★ |
let / run :r = lambda 表达式的返回值
alse / apply : x = receiver ,也就是当前的对象
use :有些需要关闭的代码,如流,等可以在 Closeable 中执行,执行完后会自动关闭
class Person(var name: String, var age: Int)fun main() {val person = Person("benny", 20)var y = person.let(::println)var x = person.run(::println)person.also {it.name = "张三"}person.apply {name = "李四"}File("build.gradle").inputStream().reader().buffered().use {//内部做了异常处理,流关闭。点击 use 查看源码println(it.readLine())}
}
集合变换与序列
集合的映射操作
函数名 | 说明 |
---|---|
filter | 保留满足的元素 |
map | 集合中的所有元素 — 映射到其他元素构成新的集合 |
flatMap | 集合中的所有元素 — 映射到新的集合并合并这些集合得到新的集合 |
fun main() {val list = listOf<Int>(1, 2, 3, 4, 5)// 集合的映射操作
// filterlist.filter { it % 2 == 0 } //2,4list.asSequence().filter { it % 2 == 0 }//2,4// mapval m1 = list.map { it * 2 + 1 } // 3,5,7,9,11val m2 = list.asSequence().map { it * 2 + 1 } // 3,5,7,9,11// flatmap
// 将一个元素映射成一个集合,再把所有的集合全部拼成一个新的集合val f1 = list.flatMap {//返回一个范围,将序列映射成一个集合0 until it}// 0 0 1 0 1 2 0 1 2 3 0 1 2 3 4}
Kt 中的懒汉序列
fun main() {val list = listOf<Int>(1, 2, 3, 4, 5)//懒序列 asSequencelist.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
filter: 5
懒序列是将集合中的元素一个个的往下执行,首先是 1,it % 2 条件不满足,接着是 2,满足,然后才会到 map 中打印出来接着变成5,然后到 forEach 中打印5。2 完了之后就是3,又从 filter 开始。一直到最后一个元素。
**注意:**懒汉序列中,如果不写 forEach ,上面的 filter 和 map 都不会执行!
Kt 中恶汉序列
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
filter: 5
map: 2
map: 4
forEach: 5
forEach: 9
结果很明显
集合的聚合操作
函数名 | 说明 |
---|---|
sum | 所有元素求和 |
reduce | 将元素依次按规矩聚合,结果与元素类型一致 |
fold | 给定初始化值,将元素按规矩聚合,结果与初始化类型一致 |
fold
// foldval f = list.fold(String()) { s: String, i: Int ->s + i}println(f) //12345
上面是执行过程
会有一个初始值,就是上面的 s ,然后和每一个元素进行拼接,最终将 s 返回。
reduce
少了初始值
val r = list.reduce() { acc, i ->acc + i}println(r) //15
可以看到,少了一个参数,不能指定类型,这里 acc 是 Int 类型
sum
val s = list.sum()
zip
fun main() {val list = listOf<Int>(1, 2, 3, 4, 5)val array = arrayOf(2, 2)val z = list.zip(array) { a: Int, b: Int ->a * b}z.forEach {println(it)} //2,4
}
结果是 2,4。为啥呢?看一哈源码就明白了:
public inline fun <T, R, V> Iterable<T>.zip(other: Array<out R>, transform: (a: T, b: R) -> V): List<V> {//拿到数组的长度val arraySize = other.size//创建一个新的集合val list = ArrayList<V>(minOf(collectionSizeOrDefault(10), arraySize))var i = 0//遍历集合,也就是调用这个方法的集合for (element in this) {//i 必须 小于数组的长度if (i >= arraySize) break//这里调用了我们传入的函数,传入当前元素,和 数组中的元素list.add(transform(element, other[i++]))}//返回一个集合return list
}
是不是非常清晰呢?
SAM 转换
SAM: Single Abstract Method ,一个抽象方法
JAVA:一个参数类型为 只有一个方法的接口的方法调用时可以使用 Lambda 表达式做转换参数
Kotlin:一个参数类型为 只有一个方法的 Java 接口的 Java 方法 ,调用时可用 Lambda 表达式作为转换参数。
例如:
一个 Java 的接口 和 类
interface Runnable {void run(int x);
}public class Main {public void execute(Runnable runnable) {}
}
在 java 中调用可以使用 Lambda 做转换
在 Kt 中调用如下
main.execute { x ->}
在 Kt 中调用 execute,可以传入一个 Lambda,而不需要 Runnable 的实例,因为在 Kt 看来,这个方法本身是可以接受两种类型的:Runnable 和 (x:Int)->Unit,前面任何一个条件不成立,这个结果就不成立,例如,Runable 是一个 Kotlin 类,或者 execute 是一个 Kotlin 方法都不可以
如果在 Kt 中定义了一个接口和 函数,应该怎么传递呢:
fun setR(r: R) {}interface R {fun run()
}fun main(){//object :R 表示创建一个匿名内部类setR(object :R{override fun run() {}})
}
通过上面这种方法即可。
Java 的 Lambda 是假的,本质上就是 SAM 转换。
Kotlin 的 Lambda 是真的,只是支持了 SAM 转换,所以在调用 java 方法的时候可以 使用 Lambda。
SAM 转换的坑
看例子:
// JAVA
public class Main {interface Runnable {void run(int x);}List<Runnable> list = new ArrayList<>();public void add(Runnable runnable) {list.add(runnable);}public void remove(Runnable runnable) {list.remove(runnable);}public int size() {return list.size();}
}
fun main() {val main = Main()//添加 Runnable// 使用 SAM 转换,其实就是创建了一个 匿名内部类main.add {println(it)}//上面这种就是如下的形式// main.add(object :Main.Runnable{// override fun run(x: Int) {// {// println(x)
// }()
// }
// })println(main.size())//删除main.remove {println(it)}println(main.size())
}
结果:1,1。没有删除掉,因为 Remove 删除的是一个新的 Lambda。不可能删除
也有一些人发现了这个问题,采用如下解决方法:
fun main() {val main = Main()val r = { i: Int ->println(i)}main.add(r)println(main.size())main.remove(r)println(main.size())
}
但是这种真的有用吗?
结果还是 :1,1
其实最终调用还是如下:
//还是一个匿名内部类
main.add(object : Main.Runnable {override fun run(x: Int) {r.invoke(x)}
})
解决
val r = object : Main.Runnable {override fun run(x: Int) {println(x)}}
直接定义一个匿名内部类,然后添加删除即可 。
案例
**练习1:**找出一个文件中每个字符出现多少次
fun main() {File("build.gradle").readText() //读文件.toCharArray()//得到字符数组.filterNot(Char::isWhitespace) //过滤空白.groupBy { it } //按照 it 分组,分组后 key 是 char,值是对应的 list// map,将元素映射为Pair.map {it.key to it.value.size}//打印.let {println(it)}
}
练习2:HTML DSL
interface Node {fun render(): String
}class StringNode(val content: String) : Node {override fun render(): String {return content}}class BlockNode(val name: String) : Node {val children = ArrayList<Node>()val properties = HashMap<String, Any>()override fun render(): String {return """<$name${properties.map { "${it.key}='${it.value}'" }.joinToString(" ")}>${children.joinToString("") { it.render() }}</$name>"""}operator fun String.invoke(block: BlockNode.() -> Unit): BlockNode {val node = BlockNode(this)node.block()this@BlockNode.children += nodereturn node}operator fun String.invoke(value: Any) {this@BlockNode.properties[this] = value}operator fun String.unaryPlus() {this@BlockNode.children += StringNode(this)}
}/*** 接收一个 BlockNode 的扩展函数* 如果传入 Lambda,这个 Lambda 中就可以调用 BlockNode 中的成员函数了*/
fun html(block: BlockNode.() -> Unit): BlockNode {val html = BlockNode("html")//调用传入的 lambdahtml.block()return html
}/*** 扩展函数*/
fun BlockNode.head(block: BlockNode.() -> Unit): BlockNode {val head = BlockNode("head")head.block()this.children += headreturn head
}/*** 扩展函数*/
fun BlockNode.body(block: BlockNode.() -> Unit): BlockNode {val body = BlockNode("body")body.block()this.children += bodyreturn body
}fun main() {val htmlContent = html {head {//重载String的 invoke 运算符,接收一个 Lambda"meta"{//重载String 的 invoke 运算符,接收一个 Any"charset"("UTF-8")}}body {"div" {"style"("""width: 200px; height: 200px; line-height: 200px; background-color: #C9394A;text-align: center""".trimIndent())"span" {"style"("""color: white;font-family: Microsoft YaHei""".trimIndent())//重载 String 的 + 运算符+"Hello HTML DSL!!"}}}}.render()File("Kotlin.html").writeText(htmlContent)
}
其实就是遍历。在 main 方法中 调用 html 方法,传入一个 lambda。在 html 方法中执行这个lambda。然后就是执行 head 方法,head 中 先执行 head 里面的方法,一直递归执行,知道执行到最后一个后才会 this.children += head。然后由内到外执行。执行完后就到了 body。也是同样的道理,递归遍历,children 集合中的数据排列顺序都是从 html 的内部一直到外部。
最终调用功能 render 函数,从外向内的递归遍历出来。
高阶函数
- 概念:参数类型包含了函数,或者返回值类型为函数。函数可被传递
- 常见的高阶函数:forEach,map
- 高阶函数的调用
内联函数
- 内联的概念:将函数放在调用处执行,提高了性能
- 内联函数的写法: inline
- 高阶函数的内联
- return,non-local-return
- 内联属性
几个有用的函数
- 按返回值的结果
- let,run
- 返回 Receiver
- also ,apply
- 自动关闭资源
- use
- 按返回值的结果
集合变换与序列
集合映射
- filter,map,flatMap
集合聚合
- fold ,reduce,sum,zip
懒序列的机制
SAM
- 匿名内部类
- SAM 概念:一个抽象方法
高阶函数
- 概念:参数类型包含了函数,或者返回值类型为函数。函数可被传递
- 常见的高阶函数:forEach,map
- 高阶函数的调用
内联函数
- 内联的概念:将函数放在调用处执行,提高了性能
- 内联函数的写法: inline
- 高阶函数的内联
- return,non-local-return
- 内联属性
几个有用的函数
- 按返回值的结果
- let,run
- 返回 Receiver
- also ,apply
- 自动关闭资源
- use
- 按返回值的结果
集合变换与序列
集合映射
- filter,map,flatMap
集合聚合
- fold ,reduce,sum,zip
懒序列的机制
SAM
- 匿名内部类
- SAM 概念:一个抽象方法
- Lambda:JAVA 中本质上就是 SAM 。Kt 中的 Lambda 就是匿名函数
参考自慕课网 Kotlin 从入门到精通
kotlin 之函数进阶相关推荐
- python 函数进阶_Python学习入门基础:一篇文章搞定函数基础、函数进阶
一.函数基础函数的快速体验 函数的基本使用 函数的参数 函数的返回值 函数的嵌套调用 在模块中定义函数私信小编001即可获取Python学习资料01. 函数的快速体验 1.1 快速体验 所谓函数,就是 ...
- 好好学python·函数进阶(递归函数,回调函数,闭包函数,匿名函数,迭代器)
函数进阶 递归函数 回调函数 闭包函数 特点 匿名函数 lambda 表达式 迭代器 iter() next() 迭代器的取值方案 迭代器取值特点,取一个少一个,直到都取完,最后再获取就会报错 检测迭 ...
- 学习Kotlin(五)函数与Lambda表达式
推荐阅读: 学习Kotlin(一)为什么使用Kotlin 学习Kotlin(二)基本语法 学习Kotlin(三)类和接口 学习Kotlin(四)对象与泛型 学习Kotlin(五)函数与Lambda表达 ...
- 41、Power Query-Text.Combine函数进阶2
本节继续讲解Power Query-Text.Combine函数进阶. 下面看一个更加有趣的例子. 比如上图,有多列,我们需要求出唯一值,标准有两个,分别是以左边为基准和以右边为基准. 比如以左边为基 ...
- 深入理解javascript函数进阶系列第一篇——高阶函数
前面的话 前面的函数系列中介绍了函数的基础用法.从本文开始,将介绍javascript函数进阶系列,本文将详细介绍高阶函数 定义 高阶函数(higher-order function)指操作函数的函数 ...
- Kotlin之函数作为参数传递
1 .Kotlin之函数作为参数传递 我们在写BaseQuickAdapter适配器的时候,有时候嵌套多个BaseQuickAdapter,如果最里面的view触发点击事件,我们可以把函数作为参数通过 ...
- python函数-函数进阶
python函数-函数进阶 一.命名空间和作用域 1.命名空间 内置命名空间 -- python解释器 就是python解释器一启动就可以使用的名字存储在内置命名空间中 内置的名字在启动解释器的时候被 ...
- function函数嵌套 matlab_Matlab函数进阶:使用匿名函数和内嵌函数处理多变量传递问题...
Matlab 函数进阶: 使用匿名函数 (Anonymous Function) 和内嵌函数 (Nested Function) 处理多变量传递问题 (Matlab 7.0 以上 ) 问题: 有一个多 ...
- Python学习入门基础:一篇文章搞定函数基础、函数进阶
一.函数基础 函数的快速体验 函数的基本使用 函数的参数 函数的返回值 函数的嵌套调用 在模块中定义函数 很多人学习python,不知道从何学起. 很多人学习python,掌握了基本语法过后,不知道在 ...
最新文章
- algorithm头文件函数全集——史上最全,最贴心
- java学习(103):字符串概述
- 博文视点大讲堂36期——让Oracle跑得更快 成功举办
- php html页面获取session,怎么在html中获取session变量
- 上海电力学院计算机组成与结构试卷,上海电力学院试卷及成绩管理办法
- 外籍专家在中关村图书大厦解密软件项目
- 阿克曼函数求解(递归和非递归)
- python数字转拼音输出,[python] pinyin 模块 -- 将汉字文本转化为拼音
- 自主研发的流程引擎怎么样?好用吗?
- java docx4j 合并word_如何使用docx4j在word中添加合并字段?
- 奥比中光深度摄像头_奥比中光展示智能深度3D摄像头技术解决方案
- 安卓(Android)手机如何安装APK?
- Linux 下的Chm 文件阅读器
- layui获取选中行数据
- java把URL转换成二维码并保存在指定的位置
- 量子计算 19 量子算法4 (Shor Part I)
- [股市]散户高手的炒股心得(收藏)
- adb 命令获取安卓设备IMEI码
- python开源web项目-最火的五大 python 开源项目
- opencv 图像梯度(python)
热门文章
- C语言复习——嵌入式相关
- 边界函数Bounding Function(成长函数的上界)
- 项目中常用的github库集合
- 使用fontawesome字体
- undefined symbol: _ZN6caffe26detail36_typeMetaDataInstance_preallocated_7E解决办法
- [目标检测]CenterNet
- Java项目:springboot酒店宾馆管理系统
- msxml document class EOleSysError with message '没有注册类别' 错误的解决
- 2021年电气试验新版试题及电气试验模拟试题
- 用云来轻APP,长江商学院EE论坛这么做