高阶函数
参数类型包含了函数,或者返回类型为函数
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: Double
inline 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)
// 集合的映射操作
// filter
list.filter { it % 2 == 0 } //2,4
list.asSequence().filter { it % 2 == 0 }//2,4
// map
val m1 = list.map { it * 2 + 1 } // 3,5,7,9,11
val 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)
//懒序列 asSequence
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
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
// fold
val 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 += node
return 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")
//调用传入的 lambda
html.block()
return html
}
/**
* 扩展函数
*/
fun BlockNode.head(block: BlockNode.() -> Unit): BlockNode {
val head = BlockNode("head")
head.block()
this.children += head
return head
}
/**
* 扩展函数
*/
fun BlockNode.body(block: BlockNode.() -> Unit): BlockNode {
val body = BlockNode("body")
body.block()
this.children += body
return 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 从入门到精通