Kotlin 朱涛-7 高阶函数 Lambda 闭包 SAM
目录
07 | 高阶函数:函数是一等公民
- StandardKt:with、let、also、takeIf、repeat、apply 源码
- CollectionsKt:map、flatMap、fold、groupBy 源码
Lambda 表达式
Lambda 简介
- Lambda 俗称
匿名函数
,也可称为闭包
,它是 Java 8 最重要的新特性。 - Lambda 可以理解为是
函数的简写
,符合一定条件的函数,就可以使用 Lambda 来简写。 - Lambda 允许把函数作为
参数
传递进方法中。
闭包(Closure)就是能够读取其他函数内部变量
的函数,所以闭包可以理解成定义在一个函数内部的函数
。
SAM 转换
SAM:Single Abstract Method,代表只有一个抽象方法的接口
public void setOnClickListener(OnClickListener l) // 接口符合 SAM 转换条件
fun mOnClick(v: View): Unit = println("onClick") // 一个普通的函数
view.setOnClickListener(::mOnClick) // 引用 mOnClick 函数
view.setOnClickListener { println("onClick") } // 进行 SAM 转换
Lambda 使用案例
下面的代码把一个 Lambda
表达式赋值给了一个变量:
fun hello(string: String) = println("hello $string") // 一个普通的方法
val say: (String) -> Unit = { s: String -> println("say $s") } // 一个闭包(Lambda)
变量 say 的类型是(String) -> Unit
fun main() {
hello("bqt") // hello bqt -- 普通方法的调用
say("bqt") // say bqt ---- 简接调用 invoke 方法
say.invoke("bqt") // say bqt ---- 直接调用 invoke 方法
}
本质是匿名内部类
反编译后 say 的等价 Java 代码为:
public final class MainKt {
private static final Function1 say; // 接口 Function1 的一个实例
static { say = (Function1)null.INSTANCE; } // 接口 Function1 的一个【匿名内部类】
public static final void main() { say.invoke("bqt"); } // 实际上调用的是接口的【invoke】方法
}
其中的 Function1
是 Kotlin 中定义的一个接口,其声明的 invoke
方法只有一个参数:
public interface Function1<in P1, out R> : kotlin.Function<R> {
public abstract operator fun invoke(p1: P1): R
}
Kotlin 中所有定义的 Function 有:
Function0、Function1、Function2...Function22
、FunctionN
所以,Lambda
表达式的底层实现,其实是匿名内部类
。
函数是一等公民
在 Kotlin 中,函数是一等公民:
- 函数也同样有
类型
-- 和类、变量一样也有类型 - 函数也可以被
引用
-- 函数也可以被赋值
、被作为函数参数
、被作为函数返回值
- 函数也可以被
嵌套
-- 闭包、Lambda - 函数可
脱离类
存在 -- 顶层函数
函数也有类型
类、变量有类型,一等公民函数当然也有。
函数类型:Function Type,是对函数的参数类型
和返回值类型
的抽象
// 函数类型 (Int, Int) -> Float
// ↑ ↑ ↑
fun add(a: Int, b: Int): Float = (a+b).toFloat()
上面的代码中,(Int, Int) -> Float
就是 add
函数的类型,它代表了参数类型是两个 Int、返回值类型为 Float 的函数类型。
函数也能引用
变量有引用、赋值的概念,一等公民函数当然也有。
fun add(a: Int, b: Int): Int = a + b
val function: (Int, Int) -> Int = ::add // 将函数赋值给变量,【::add】代表函数【add】的引用
val runnable = Runnable { print("x") }
val block: () -> Unit = runnable::run // 【runnable::run】代表对象【runnable】的【run】函数的引用
高阶函数 High-order
高阶函数:High-order functions,是指使用函数作为参数或返回值
的函数
在特定业务场景下,可以用高阶函数来实现自己的 DSL
(Domain Specific Language)
fun main() {
val runnable = Runnable { println("run") } // 一个实现 Runnable 接口的普通对象
val block: () -> Unit = runnable::run // 引用 runnable 对象的 run 方法
runnable.run()
block()
block.invoke()
fun function1(b: () -> Unit): Unit = b() // 高阶函数,使用函数作为参数
fun function2(b: () -> Unit): Unit = b.invoke() // 高阶函数,使用函数作为参数
function1(block)
function2(block)
fun function3(): () -> Unit = runnable::run // 高阶函数,使用函数作为返回值
fun function4(): () -> Unit = block // 高阶函数,使用函数作为返回值
function3()
function4()
}
高阶函数的本质是匿名内部类
高阶函数是 Lambda 的一种应用场景,Lambda 的本质是匿名内部类,所以高阶函数的本质也是匿名内部类。
Lambda 表达式的几种写法
① object 匿名内部类
这是原始代码,它的本质是用 object 关键字定义了一个匿名内部类
:
image.setOnClickListener(object: View.OnClickListener { // 匿名内部类
override fun onClick(v: View) = gotoPreview(v)
})
② Lambda 表达式
下面是 Lambda
表达式的原始代码:
image.setOnClickListener(View.OnClickListener { v: View -> // Lambda 表达式
gotoPreview(v)
})
③ 省略 SAM 构造器
上面的 View.OnClickListener
被称为 SAM Constructor
,Kotlin 的 Lambda 表达式中可以省略:
image.setOnClickListener({ v: View -> // 省略 Lambda 表达式中的 SAM Constructor
gotoPreview(v)
})
④ 支持自动类型推导
Kotlin 支持类型推导
,所以类型 View 可以省略:
image.setOnClickListener({ v -> // 省略类型 View
gotoPreview(v)
})
⑤ 省略 Lambda 的参数 ★
当 Kotlin Lambda 表达式只有一个参数的时候,声明中可以省略这个参数,在使用时可以用 it
替代那个参数:
image.setOnClickListener({ // 声明中可以省略这个参数
gotoPreview(it) // 在使用时可以用 it 替代那个参数
})
⑥ 移动 Lambda 位置 ★
当 Kotlin Lambda 作为函数的 最后一个参数 时,Lambda 可以被挪到外面:
image.setOnClickListener() { gotoPreview(it) } // 移动 Lambda 位置
⑦ 省略 Lambda 括号 ★
当 Kotlin 只有一个 Lambda 作为函数参数时,()
可以被省略:
image.setOnClickListener { gotoPreview(it) } // 省略 Lambda 小括号
带接收者的函数类型
apply 函数的使用
使用 Kotlin 的标准函数 apply
可以简化代码。
data class User(var name: String, var blog: String)
val user = User("bqt", ".blog.com")
println(user.name + user.blog) // 显示调用 user
user.apply {
println(name + this.blog) // 借助 apply 可以使用 this 代替 user(this 通常可以省略)
}
普通扩展函数案例
现在,我们尝试自己实现这个 apply 方法:
fun User.apply2(block: (user: User) -> Unit): User {
block(this)
return this // 返回调用者自身
}
user.apply2 {
println(it.name + it.blog) // 只能通过 it 访问成员,而不能通过 this 访问成员
}
和 Kotlin 的标准函数 apply
相比,我们自定义的 apply2
不能通过 this
访问成员。
带接收者的函数类型案例
使用 带接收者的函数类型,可以简化上面 apply2 函数的定义:
fun User.apply3(block: User.() -> Unit): User { // block 中的参数提取到了外面
block() // 不用再传 this
return this
}
核心变化:把参数类型 User 提取到了参数列表外面
user.apply3 {
println(this.name + blog) // 可以通过 this 访问成员了
}
总结
带接收者的函数类型
也算是一种扩展函数
,从语法层面讲,他们都相当于成员方法
。但从本质上讲,它是通过编译器注入 this 来实现的。
小结
2016-12-05
本文来自博客园,作者:白乾涛,转载请注明原文链接:https://www.cnblogs.com/baiqiantao/p/6133527.html