End

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...Function22FunctionN

所以,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

posted @ 2016-12-05 12:58  白乾涛  阅读(5180)  评论(0编辑  收藏  举报