likes
comments
collection
share

从 Kotlin Lambda 开始说起

作者站长头像
站长
· 阅读数 16

不管有没有用过 Kotlin,不少人应该听过一个词,叫函数式编程,简单来说在函数式编程语言里,函数可以作为别的函数的参数、函数的返回值「参数或返回值为函数的函数也被称为高阶函数,仅仅是个概念,了解就行」值给变量或存储在数据结构中。在这样的语言中,函数的名字没有特殊含义,它们被当作具有函数类型的普通的变量对待。

Kotlin 语言支持函数式编程,为促成这点,作为一门静态类型编程语言的 Kotlin 使用一系列函数类型来表示函数并提供一组特定的语言结构,例如 lambda 表达式

函数类型

因为 Kotlin 支持函数作为参数或返回值,那意味着函数类型与其他类型一样(如:ListString),它们一样可以被声明、初始化等等。比如我们声明并初始化一个List,我们是这样写的:

val list: List = ArrayList()

= 左边是定义变量类型,右边是初始化赋值。这很简单,学过任何编程语言的应该都能看得懂。那函数类型是如何声明与初始化?我们来看看。

函数类型声明

Kotlin 使用类似(类型) -> 类型的结构来声明函数类型,以->为分割符,左边是函数参数列表及类型,右边是函数返回类型。

  • 所有函数类型都有一个圆括号括起来的参数类型列表以及一个返回类型:(A, B) -> C 表示接受类型分别为 A 与 B 两个参数并返回一个 C 类型值的函数类型。 参数类型列表可以为空,如 () -> AUnit 返回类型不可省略。
  • 函数类型可以有一个额外的接收者类型,它在表示法中的点之前指定: 类型 A.(B) -> C 表示可以在 A 的接收者对象上以一个 B 类型参数来调用并返回一个 C 类型值的函数。 带有接收者的函数字面值通常与这些类型一起使用。
  • 挂起函数属于特殊种类的函数类型,它的表示法中有一个 suspend 修饰符 ,例如 suspend () -> Unit 或者 suspend A.(B) -> C

如需将函数类型指定为可空,请使用圆括号:((Int, Int) -> Int)?

函数类型可以使用圆括号进行接合:(Int) -> ((Int) -> Unit)

箭头表示法是右结合的,(Int) -> (Int) -> Unit 与前述示例等价,但不等于 ((Int) -> (Int)) -> Unit

函数类型实例化

有几种方法可以获得函数类型的实例

使用函数字面值的代码块

  • lambda 表达式: { a, b -> a + b }

  • 匿名函数fun(s: String): Int { return s.toIntOrNull() ?: 0 }

lambda 与匿名函数都可以用来实例化函数类型,它们可以相互转换,大多数情况是完全等价的

// Lambda 表达式  
val fun1: (Int) -> Unit = { p: Int ->  }  
  
// 匿名函数  
val fun2: (Int) -> Unit = fun (p: Int) { }

这两种写法是完全等价的,Lambda 表达式可以被视为是匿名函数的简单写法,并且它还可以更简单。

  1. 可以省略参数类型
  2. 如果参数只有一个,那这个参数也可以省略,lambda 有一个默认参数名 it

上面的 Lambda 可以写成这样:val fun1: (Int) -> Unit = { }

大部分接触 Kotlin 的程序员都是从 Java 过来的,Java 里面我们都知道匿名类,没听过有匿名函数

class AnonymousType{ }

public static void main(String[] args) {  
    // 实例化 AnonymousType
    AnonymousType a = new AnonymousType();  
    // 后面加花括号之后它就不是 AnonymousType 类型了,而是 AnonymousType 的子类
    // 且这个子类是没有显示名称的,
    // 最常见使用匿名类的场景就是,当声明类型是接口、抽象类时,不想显示声明接口、抽象类
    // 的子类,所以会实例化一个匿名类类型
    AnonymousType a2 = new AnonymousType(){};  
}

匿名函数匿名类是比较相似的,刚从 Java 过来,不了解 Kotlin 的函数类型,所以更不了解匿名函数。但实际上它们本质上是一样的,使用场景都是声明某个类型(函数类型),但又不想显示声明某个函数,所以便使用了匿名函数

如果你把 Kotlin 的代码 decode 成 Java,那你会惊喜的发现,本质上函数类型对应的就是一个接口,初始化的时候使用的就是匿名类。

使用已有声明的可调用引用

  • 顶层、局部、成员、扩展函数:::isOdd、 String::toInt

  • 顶层、成员、扩展属性:List<Int>::size

  • 构造函数:::Regex

这包括指向特定实例成员的绑定的可调用引用:foo::toString

Kotlin 可以用双冒号"::"表示对方法、属性的引用。举个例子,可能有人刚接触 lambda 、匿名函数感觉还是会陌生,那么用::引用函数(Java 中称为方法,Kotlin叫函数,本质都一样,没区别)会让你更熟悉些,这种方式相当于是实例化了一个匿名函数。

// 方法引用
val fun3: (Int) -> Unit = ::func1

fun func1(p: Int) { }

这种方式跟上述的两种函数实例化也是一样的,完全等价。如果你刚开始 lambda 不习惯,也可以使用这样的方式。fun1就是一个普通方法,如果这个方法的参数与返回类型与声明的函数类型一致,那它就可以被引用来实例化定义的函数。

Lambda /匿名函数的应用

有这样一个高阶函数,需要一个函数和一个Int类型参数:

fun combine(block: (String, String) -> String, p2: Int): String {  
    val a = "RandomA" // generate string a  
    val b = "RandomB" // generate string b  
    return "${block(a, b)} - ${p2 * 5}"  
}

我们看看怎么调用combine这个函数

fun main() {  
    // Lambda 
    val a = combine({ a, b ->  
        "$a *** $b"  
    }, 5)  
    // 匿名函数
    val b = combine(fun(a, b): String {  
        return "$a *** $b"  
    }, 5)  

    fun func1(a: String, b: String): String {  
        return "$a *** $b"  
    }  
    // 函数引用
    val c = combine(::func1, 5)  

    println(a)  
    println(b)  
    println(c)  
}  

可以看到 lambda 的调用是最简单的,lambda 一般是把最后一行表达式类型作为返回类型,而匿名函数一般则需要显示的写出 return,而 lambda 在某些时候还可以更简单。

  • 函数类型参数一般放到最后一个,这样便可以把 lambda 移到括号外面
fun main() { 
    // 是不是看起来便优雅了?
    combine(5) { a, b ->  
        "$a *** $b"  
    }  
    // 如果某个参数不用,可以用 “_” 表示
    combine(5) { _, _ ->  
        "Hello lambda"  
    }
}  
  
fun combine(p2: Int, block: (String, String) -> String): String {  
    val a = "RandomA" // generate string a  
    val b = "RandomB" // generate string b  
    return "${block(a, b)} - ${p2 * 5}"  
}
  • 如果函数类型的参数只有一个,则在 lambda 中可以省略,kotlin 会用默认参数 it 表示
fun main() {  
    // 这样的代码是不是清爽多了
    combine(5) {  
        "$it *** $"  
    }  
}  
  
fun combine(p2: Int, block: (String) -> String): String {  
    val a = "RandomA" // generate string a  
    val b = "RandomB" // generate string b  
    return "${block(a)} - ${p2 * 5}"  
}

这些 lambda 的简便规则在匿名函数上是不存在的,意味着使用匿名函数在代码风格上是没有 lambda简单、清爽的。

总结

这就总结了吗?貌似主要讲的是 Kotlin 的函数类型呀?

是的 lambda 就是实例化函数类型的一种方式。Kotlin 语言里函数能够作为函数的参数、返回值类型的本质原因是 Kotlin 有函数类型这个类型。而 lambda是实例化函数类型的一种方式,它与匿名函数函数引用作用本质上是一样的,区别是 lambda在使用上更简单、方便。

在大多数情况下,lambda可以视为是匿名函数的一种更简单写法,你如果不习惯这种看不到参数数据类型的写法,也可以使用匿名函数,它们是一样的。

函数类型是 Kotlin 区别于 Java 的一个重要差异,虽然本质上的函数类型还是用接口来实现的,但是在用法上是确确实实给开发者带来了方便,也让代码更简洁。

完。