Kotlin教程(六)Lambda编程

本文是Kotlin教程的第六部分,主要讲解Lambda编程,包括Lambda表达式的基本用法、成员引用、集合的函数式API、序列操作等。介绍了如何简化代码,如将匿名内部类替换为Lambda表达式,以及如何使用filter、map等函数对集合进行操作。还探讨了Lambda表达式的语法、如何访问和修改外部变量,以及如何将Lambda转换为Java函数式接口。此外,文章还提到了序列(惰性集合操作)的概念,展示了如何避免创建不必要的中间集合,提高性能。
摘要由CSDN通过智能技术生成

写在开头:本人打算开始写一个Kotlin系列的教程,一是使自己记忆和理解的更加深刻,二是可以分享给同样想学习Kotlin的同学。系列文章的知识点会以《Kotlin实战》这本书中顺序编写,在将书中知识点展示出来同时,我也会添加对应的Java代码用于对比学习和更好的理解。

Kotlin教程(一)基础
Kotlin教程(二)函数
Kotlin教程(三)类、对象和接口
Kotlin教程(四)可空性
Kotlin教程(五)类型
Kotlin教程(六)Lambda编程
Kotlin教程(七)运算符重载及其他约定
Kotlin教程(八)高阶函数


Lambda表达式,或简称lambda,本质上级就是可以传递给其他函数的一小段代码。有了lambda,可以轻松地把通用的代码结构抽取成库函数,Kotlin标准库就大量地使用了它们。

Lambda表达式和成员引用

把lambda引入Java 8是Java这门语言演变过程中让人望眼欲穿的变化之一。为什么它是如此重要?这一节中,你会发现为何lambda这么好用,以及Kotlin的lambda语法看起来是什么样子的。

Lambda简介:作为函数参数的代码块

在你代码中存储和传递一小段行为是常有的任务。例如,你常常需要表达像这样的想法:“当一个时间发生的时候运行这个事件处理器”又或者是“把这个操作应用到这个数据接口中所有元素上”。在老版本的Java中,可以使用匿名内部类来实现。这种技巧可以工作但是语法太啰嗦了。
函数式编程提供了另外一种解决问题的方法:把函数当做值来对待。可以直接传递函数,而不需要先声明一个类再传递这个类的实例。使用lambda表达式之后,代码会更加简洁。都不需要声明函数了,可以高效地直接传递代码块作为函数参数。
我们来看一个例子。假设你要定义一个点击按钮的行为,添加一个负责处理点击的监听器。监听器实现了相应的接口OnClickListener和它的一个方法onClick:

/* Java */
button.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            //do something
        }
});

这样声明匿名内部类的写法实在是太啰嗦了。在Kotlin中我们可以像Java 8一样使用lambda来消除这些冗余代码。

/* Kotlin */
button.setOnClickListener{ /* do someting */ }

这段代码做了与上面同样的事情,但是不用再写啰嗦的匿名内部类了。
之前也说过Kotlin可以使用关键字object 匿名内部类,因此,你想写成普通的方式也是可以的:

button.setOnClickListener(object : View.OnClickListener {
            override fun onClick(v: View?) {
                println("on click")
            }
        })

上面两种方式转换成Java代码:

button.setOnClickListener((OnClickListener)null.INSTANCE);
button.setOnClickListener((OnClickListener)(new OnClickListener() {
     public void onClick(@Nullable View v) {
        String var2 = "on click";
        System.out.println(var2);
     }
  }));

匿名内部类转换成了Java的匿名内部类。但是lambda应该是Kotlin自己做了特出处理,无法转换成相应的Java代码。

Lambda和集合

我们先来看一个例子,你会用到一个Person类,它包含这个人的名字和年龄信息:

data class Person(val name: String, val age: Int)

假设现在你有一个人的列表,需要找到列表中年龄最大的那个人。如果完全不了解lambda,你可能会这样做:

fun findTheOldest(people: List<Person>) {
    var maxAge = 0
    var theOldest: Person? = null
    for (person in people) {
        if (person.age > maxAge) {
            maxAge = person.age
            theOldest = person
        }
    }
    println(theOldest)
}

>>> val people = listOf(Person("Alice", 29), Person("Hubert", 26))
>>> findTheOldest(people)
Person("Alice", 29)

可以完成目的,但是代码稍微有点多。而Kotlin有更好的方法,可以使用库函数:

>>> val people = listOf(Person("Alice", 29), Person("Hubert", 26))
>>> println(people.maxBy{ it.age })
Person("Alice", 29)

maxBy函数可以在任何集合上调用,且只需要一个实参:一个函数,指定比较哪个值来找到最大元素。花括号中的代码{ it.age } 就是实现了这个逻辑的lambda。它接收一个集合中的元素作为实参(使用it引用它)并且返回用来比较的值。这个例子中,集合元素是Person对象,用来比较的是存储在其age属性中的年龄。
如果lambda刚好是函数或者属性的委托,可以用成员引用替换:

people.maxBy{ Person::age }

虽然lambda看上去很简洁,但是你可能不是很明白到底是如何写lambda,以及里面的规则,我们来学习下lambda表达式的语法吧。

Lambda表达式的语法

一个lambda把一小段行为进行编码,你能把它当做值到处传递。它可以被独立地声明并存储到一个变量中。但是更常见的还是直接声明它并传递给函数。

   //参数           //函数体
{ x: Int, y: Int -> x + y }

Kotlin的lambda表达式始终用花括号包围。->把实参和函数体分割开,左边是参数列表,右边是函数体。注意参数并没有用() 括起来。
可以把lambda表达式存储在一个变量中,把这个变量当做普通函数对待(即通过相应实参调用它):

>>> val sum = {x:Int,y:Int -> x + y}
>>> println(sum(1, 2))
3

如果你乐意,还可以直接调用lambda表达式:

>>> { println(42) }()
42

但是这样的语法毫无可读性,也没有什么意义(它等价于直接执行lambda函数体中的代码)。如果你确实需要把一小段代码封闭在一个代码块中,可以使用库函数run来执行传递它的lambda:

>>> run{ println(42) }
42

在之后的章节我们会了解到这种调用和内建语言结构一样高效且不会带来额外运行时开销,以及背后的原因。现在我们继续看“找到列表中年龄最大”的例子:

>>> val people = listOf(Person("Alice", 29), Person("Hubert", 26))
>>> println(people.maxBy{ it.age })
Person("Alice", 29)

如果不用任何简明语法来重写这个例子,你会得到下面的代码:

people.maxBy({ p: Person -> p.age })

这段代码一目了然:花括号中的代码片段是lambda表达式,把它作为实参传给函数。这个lambda接收一个类型为Person的参数并返回它的年龄。
但是这段代码有点啰嗦。首先,过多的标点符号破坏了可读性。其次,类型可以从上下文推断出来并可以省略。最后,这种情况下不需要给lambda的参数分配一个名称。
让我们来改进这些地方,先拿花括号开刀。Kotlin有这样一种语法约定,如果lambda表达式是函数调用的最后一个实参,它可以放到括号的外边。这个例子中,lambda是唯一的实参,所以可以放到括号的后边:

people.maxBy() { p:Person -> p.age }

当lambda时函数唯一的实参时,还可以去掉调用代码中的空括号:

people.maxBy { p:Person -> p.age }

三种语法形式含义都是一样的,但最后一种最易读。如果lambda是唯一的实参,你当然愿意在写代码的时候省掉这些括号。而当你有多个实参时,即可以把lambda留在括号内来强调它是一个实参,也可以把它放在括号的外面,两种选择都是可行的。如果你想传递两个更多的lambda,不能把超过一个lambda放在外面。
我们来看看这些选项在更复杂的调用中是怎样的。还记得外面在教程二中定义的joinToString函数吗?Kotlin标准库中也有定义它,不同之处在于它可以接收一个附加的函数参数。这个函数可以用toString函数以外的方法来把一个元素转换成字符串。下面的例子展示了你可以用它只打印出人的名字:

>>> val names = people.joinToString(separator = " ", transform = { p: Person -> p.name })
>>> println(names)
Alice Hubert

这种方式使用命名实参来传递lambda,清楚地表示了lambda应用到了哪里。
下面的例子展示课可以怎样重写这个调用,把lambda放在括号外:

>>> val names = people.joinToString(" ") { p: Person -> p.name }
>>> println(names)
Alice Hubert

这种方式没有显式地表明lambda引用到了哪里,所以不熟悉被调用函数的那些人可能更难理解。

在as或者IDEA中可以使用Alt+Enter唤起操作,使用“Move lambda expression out of parentheses ”把lambda表达式移动到括号外,或“Move lambda expression into parentheses”把lambda表达式移动到括号内。

我们继续简化语法,移除参数的类型。

people.maxBy { p:Person -> p.age }
people.maxBy { p -> p.age }  //推导出参数类型

和局部变量一样,如果lambda参数的类型可以被推导出来,你就不需要显示地指定它。以这里的maxBy函数为例,其参数类型始终和集合的元素类型相同。编译器知道你是对一个Person对象的集合调用maxBy函数,所以它能推导lambda参数也会是Person类型。
也存在编译器不能推断出lambda参数类型的情况,但这里我们暂不讨论。可以遵循这样的一条简单的规则:先不声明类型,等编译器报错后再来指定它们。
这个例子你能做的最后简化是使用默认参数名称it代替命名

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值