《Kotlin核心编程》笔记:函数和Lambda表达式

Posted 川峰

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《Kotlin核心编程》笔记:函数和Lambda表达式相关的知识,希望对你有一定的参考价值。

高阶函数和lambda表达式

函数式语言⼀个典型的特征就在于函数是头等公民——我们不仅可以像类⼀样在顶层直接定义⼀个函数,也可以在⼀个函数内部定义⼀个局部函数,如下所示:

 所谓的高阶函数,你可以把它理解成“ 以其他函数作为参数或返回值的函数” 。

函数类型

在Kotlin中,函数类型的格式非常简单,举个例子:

 从中我们发现,Kotlin中的函数类型声明需遵循以下几点:

  • 通过->符号来组织参数类型和返回值类型,左边是参数类型,右边是返回值类型;
  • 必须用⼀个括号来包裹参数类型
  • 返回值类型即使是Unit,也必须显式声明
如果是一个没有参数的函数类型,参数类型部分就用()来表示。

 如果是多个参数的情况,那么我们就需要用逗号来进行分隔,如:

 此外,Kotlin还支持为声明参数指定名字,如下所示:

 支持可空类型:

 如果该函数类型的变量也是可选的话,我们还可以把整个函数类型变成可选:

 函数类型作为一个函数的返回值(高阶函数)

这表示传入⼀个类型为Int的参数,然后返回另⼀个类型为(Int)->Unit的函数。

方法和成员引用

Kotlin存在⼀种特殊的语法,通过两个冒号来实现对于某个类的方法进行引用。 以上面的代码为例,假如我们有⼀个CountryTest类的对象实例countryTest,如果要引用它的isBigEuropeanCountry方法,就可以这么写:

 此外,我们还可以直接通过这种语法,来定义⼀个类的构造方法引用变量。

其中getBook的类型为(name: String)-> Book,类似的可以引用类中的某个成员变量,如:

这对于在对Book类对象的集合应用⼀些函数式API的时候,会显得格外有用,比如:

匿名函数

 

lambda表达式

lambda是一种匿名函数,它是一种lambda语法糖

 现在用Lambda的形式来定义一个加法操作:

 Lambda语法总结:

  • ⼀个Lambda表达式必须通过来包裹;
  • 如果Lambda声明了参数部分的类型,且返回值类型支持类型推导,那么Lambda变量就可以省略函数类型声明;
  • 如果Lambda变量声明了函数类型,那么Lambda的参数部分的类型就可以省略。
此外,如果Lambda表达式返回的不是Unit,那么默认最后⼀行表达式的值类型就是返回值类型,如:

单个参数的隐式名称:it

 它也是Kotlin简化Lambda表达的⼀种语法糖,it是item的缩写。

Function类型

Kotlin在JVM层设计了Function类型(Function0、Function1……Function22)主要目的是用来兼容Java的Lambda表达式,其中的后缀数字代表了Lambda参数的数量,如以上的foo函数构建的其实是⼀个⽆参Lambda,所以对应的接口是Function0,如果有⼀个参数那么对应的就是Function1。它在源码中是如下定义的:

 可见每个Function类型都有一个invoke方法。

Java中,实际上并不支持把函数作为参数,而是通过函数式接口来实现这⼀特性。所以如果我们要把Java的Lambda传给Kotlin,那么它们就必须实现Kotlin的Function接口,在Kotlin中我们则不需要跟它们打交道。(当然在Java中可以使用JDK8支持的MethodHandle动态语言实现给方法传递一个函数,它的原理是基于字节码指令的封装调用)              神奇的数字—22         也许你会问⼀个问题:为什么这里Function类型最大的是Function22?如果Lambda的参数超过了22个,那该怎么办呢? 前面例子的代码清单中的 foo函数的返回类型是 Function0。这也意味着,如果我们调用了 foo(n),那么实质上仅仅是构造了⼀个 Function0对象。这个对象并不等价于我们要调用的过程本身。通过源码可以发现,需要调用 Function0invoke方法才能执行println方法。所以, 必须如下修改,才能够最终打印出我们 想要的结果:

在Kotlin中,我们还可以用更加简洁的方式,即使用括号调用来替代invoke,如下所示:

函数、Lambda和闭包

  • fun在没有等号、只有花括号的情况下,是我们最常见的代码块函数体,如果返回非Unit值,必须带return。

  • fun带有等号,是单表达式函数体。该情况下可以省略return。

  • 不管是用val还是fun,如果是等号加花括号的语法,那么构建的就是⼀个Lambda表达式,Lambda的参数在花括号内部声明。所以,如果左侧是fun,那么就是Lambda表达式函数体,也必须通过()或 invoke来调用Lambda,如:

在Kotlin中,你会发现匿名函数体、Lambda(以及局部函数、object表达式)在语法上都存在“ ”,由这对花括号包裹的代码块如果访问了外部环境变量则被称为⼀个闭包。⼀个闭包可以被当作参数传递或者直接使用,它可以简单地看成“ 访问外部环境变量的函数” 。Lambda是Kotlin中最常见的闭包形式。与Java不⼀样的地方在于,Kotlin中的闭包不仅可以访问外部变量,还能够对其进行修改。

类“柯里化”风格

“柯里化”语法其实就是 函数作为返回值的一种典型应用。简单来说,柯里化指的是把接收多个参数的函数变换成⼀系列仅 接收单⼀参数函数的过程,在返回最终结果值之前,前面的函数依次 接收单个参数,然后返回下⼀个新的函数。     说到底,柯里化是为了简化Lambda演算理论中函数接收多参数而 出现的,它简化了理论,将多元函数变成了⼀元。     Lambda表达式中,还存在⼀种特殊的语 法。 如果⼀个函数只有⼀个参数,且该参数为函数类型,那么在调用 该函数时,外面的括号就可以省略 ,就像这样子:

 此外,如果参数不止⼀个,且最后⼀个参数为函数类型时,就可以采用类似柯里化风格的调用

 它等价于以下的的调用方式:

函数可变参数

Kotlin通过 varargs关键字来定义函数中的可变参数,类似Java中的 “  ”  的效果。需要注意的是, Java 中的可变参数必须是最后⼀个参 数, Kotlin 中没有这个限制 ,但两者都可以在函数体中以 数组 的方式 来使用可变参数变量。

 此外,我们可以使用 *(星号)来传入外部的变量作为可变参数的变,如下:

 

可选参数

只要为函数参数提供默认值即可实现可选参数  
fun foo(a: Int, b: Int = 10) 


foo(2)
foo(2, 4)

当每个参数都有默认值时,需要指定参数名:


fun foo(a: Int = 3, b: Int = 10) 


foo(b = 2)
foo(a = 2, b = 4)

调用Java的函数式接口

android开发时,我们经常会遇到给视图绑定点击事件的场 景。以往通常的做法如下:
view.setOnClickListener(new OnClickListener() 
    @Override
    public void onClick(View v) 
        …
    
)

以上的例子在Kotlin会被转化成这样:

view.setOnClickListener(object : OnClickListener 
    override fun onClick(v: View) 
        …
    
)

Kotlin允许对Java的类库做一些优化,任何函数接收了一个Java的SAM(单一抽象方法)都可以用Kotlin的函数进行替代。以上的例子我们可以看成在Kotlin定义了以下方法:

 listener是一个函数类型的参数,它接收一个类型View的参数,然后返回Unit。我们可以用Lambda语法来简化它:

由于Kotlin存在特殊语法糖,这里的listener是setOnClickListener唯一的参数,所以我们就可以省略掉括号

带接收者的Lambda

在Kotlin中,我们可以定 义带有接收者的函数类型,如:

 此时,我们就可以用一个Int类型的变量调⽤sum方法,传入一个Int类型的参数,对其进行plus操作。

Kotlin 有一种神奇的语法 —— 类型安全构造器 ,用它可以构造类型安全的 html 代码,带接收者的 Lambda 语法可以很好地应用到其中。
class HTML 
    fun body() ...

fun html(init: HTML.()->Unit): HTML 
    val html = HTML() // 创建了接收者对象
    html.init() // 把Lambda传递给接收者对象
    return html


html 
    body() // 调用接收者对象的body方法

withapply

Kotlin的库中还实现了两个非常好用的函数: withapply。将它 们与带接收者的 Lambda 结合,可以在某些场合进一步简化语法。这两个方法最大的作用就是可以让我们在写Lambda的时候,省 略需要多次书写的对象名,默认用 this 关键字来指向它。     比如,在用Android开发时,我们经常会给一些视图控件绑定属 性。以下我们利用 with 让代码可读性变得更好。
fun bindData(bean: ContentBean) 
    val titleTV = findViewById<TextView>(R.id.iv_title)
    val contentTV = findViewById<TextView>(R.id.iv_content)
    with(bean) 
        titleTV.text = this.title // this可以省略
        titleTV.textSize = this.titleFontSize
        contentTV.text = this.content
        contentTV.text = this.contentFontSize
    

如果不使用with,我们就需要写好多遍bean。现在来看看with在Kotlin库中的定义:

可以看出,with函数的第1个参数为接收者类型,然后通过第2个参数创建这个类型的block方法。

因此在该接收者对象调用 block方法 时,可以在 Lambda 中直接使用 this 来代表这个对象。 我们再来看看apply函数是如何定义的:

with函数不同,apply直接被声明为类型T的一个扩展方法,它的block参数是一个返回Unit类型的函数,作为对比,withblock则可以返回自由的类型。

然而二者在很多情况下是可以互相替代的。我们 可以很容易地把上面的代码翻译成 apply 的版本:

以上是关于《Kotlin核心编程》笔记:函数和Lambda表达式的主要内容,如果未能解决你的问题,请参考以下文章

kotlin学习笔记之闭包

kotlin学习笔记之闭包

Kotlin小笔记Lambda和集合的函数API

kotlin 实战之函数与 lambda 表达式总结

Kotlin 学习笔记—— 基本类型函数lambda类与对象的写法

kotlin学习笔记之Lambda表达式