Android开发——Kotlin语法之Lambda表达式

Lambda表达式在C++/Java中都支持,作为一种辅助语句。但是在Kotlin中,Lambda表达式的功能极其强大。
简化是Kotlin语言的思想之一,而在Lambda表达式的学习之中,该思想将会贯穿其中

Lambda入门编程

集合的函数式API是用来入门Lambda编程的绝佳示例,不过在此之前,我们得先学习创建集合的方式才行

集合的创建

在Kotlin中,提供了一个listof() 的内置函数来帮助我们创建一个集合,用法如下:

val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")

这里,我们创建了一个水果的集合。值得注意的是,我们使用的listof() 函数创建的是一个不可变的集合,该集合只能用于读取,我们无法对集合进行添加、修改或删除操作。如果我们需要创建一个可变的集合,我们可以使用mutableListof() 函数。使用方法与listof相同。

集合的函数式API

首先我们来思考一个需求,如何在一个水果集合里面找到单词最长的那个水果?在Kotlin中,我们可以写出如下代码:

val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape", "Watermelon")
val maxLengthFruit = list.maxBy { it.length }
println("max length fruit is " + maxLengthFruit)

这里使用了函数式APImaxBy()。 maxBy是一个函数,只不过它接收的是一个Lambda类型的参数,并且会在遍历集合时将每次遍历的值作为参数传递给Lambda表达式。maxBy函数的工作原理是根据我们传入的条件来遍历集合,从而找到该条件下的最大值,比如说想要找到单词最长的水果,那么条件自然就应该是单词的长度了。因此,我们可以推断出 it.lenth就是一个Lambda表达式。更确切地说,这是简化后的Lambda表达式。

Lambda表达式的语法结构

首先来看一下Lambda的定义,如果用最直白的语言来阐述的话,Lambda就是一小段可以作为参数传递的代码。从定义上看,这个功能就很厉害了,因为正常情况下,我们向某个函数传
参时只能传入变量,而借助Lambda却允许传入一小段代码,其语法结构如下:

{参数名1: 参数类型, 参数名2: 参数类型 -> 函数体}

值得注意的是,Lambda表达式的最后一行代码会自动作为Lambda表达式的返回值。

Lambda表达式的简化

这一部分,我们来介绍Lambda表达式简化的一些规则。
对于找到单词最长的水果,我们一开始可以写出这样的代码:

val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape", "Watermelon")
val lambda = { fruit: String -> fruit.length }
val maxLengthFruit = list.maxBy(lambda)

首先,我们不需要专门定义一个lambda变量,而是可以直接将lambda表达式传入maxBy函数当中,因此第一步简化如下所示:

val maxLengthFruit = list.maxBy({ fruit: String -> fruit.length })

简化规则1:当Lambda参数是函数的最后一个参数时,可以将Lambda表达式移到函数括号的外面

根据简化规则1,我们继续将该行代码简化:

val maxLengthFruit = list.maxBy() { fruit: String -> fruit.length }

简化规则2:如果Lambda参数是函数的唯一一个参数的话,还可以将函数的括号省略

根据简化规则2,我们继续将该行代码简化:

val maxLengthFruit = list.maxBy { fruit: String -> fruit.length }

简化规则3:由于Kotlin出色的类型推导机制,Lambda表达式中的参数列表其实在大多数情况下不必声明参数类型

根据简化规则3,我们继续将该行代码简化:

val maxLengthFruit = list.maxBy { fruit -> fruit.length }

简化规则4:当Lambda表达式的参数列表中只有一个参数时,也不必声明参数名,而是可以使用it关键字来代替

根据简化规则3,我们继续将该行代码简化:

val maxLengthFruit = list.maxBy { it.length }

于是乎,我们就得到了与开头我们提供的代码一模一样的代码。

其它的函数式API

map函数:它用于将集合中的每个元素都映射成一个另外的值,映射的规则在Lambda表达式中指定,最终生成一个新的集合。

如下所示的代码是将所有的水果名都变成大写模式:

fun main() {
	val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape", "Watermelon")
	val newList = list.map { it.toUpperCase() }
	for (fruit in newList) {
		println(fruit)
	}
}

filter函数:用来过滤集合中的数据,过滤的规则在Lambda表达式中指定

如下所示的代码是保留5个字母以内的水果,并将其单词全部大写:

fun main() {
	val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape", "Watermelon")
	val newList = list.filter { it.length <= 5 }
					  .map { it.toUpperCase() }
	for (fruit in newList) {
		println(fruit)
	}
}

any函数:用于判断集合中是否至少存在一个元素满足指定条件,条件在Lambda表达式中指出

all函数:用于判断集合中是否所有元素都满足指定条件,条件在Lambda表达式中指出

any函数和all函数的用法如下:

fun main() {
	val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape", "Watermelon")
	val anyResult = list.any { it.length <= 5 }
	val allResult = list.all { it.length <= 5 }
	println("anyResult is " + anyResult + ", allResult is " + allResult)
}

Kotlin调用Java方法

在Kotlin中调用Java方法时也可以使用函数式API,只不过这是有一定条件限制的。具体来讲,如果我们在Kotlin代码中调用了一个Java方法,并且该方法接收一个Java单抽象方法接口参数,就可以使用函数式API。Java单抽象方法接口指的是接口中只有一个待实现方法,如果接口中有多个待实现方法,则无法使用函数
式API。
Java原生API中有一个最为常见的单抽象方法接口——Runnable接口。这个接口中只有一个待实现的run()方法,定义如下:

public interface Runnable {
	void run();
}

根据前面的讲解,对于任何一个Java方法,只要它接收Runnable参数,就可以使用函数式API。那么什么Java方法接收了Runnable参数呢?这就有很多了,不过Runnable接口主要还
是结合线程来一起使用的,因此这里我们就通过Java的线程类Thread来学习一下。
Thread类的构造方法中接收了一个Runnable参数,我们可以使用如下Java代码创建并执行一个子线程:

new Thread(new Runnable() {
	@Override
	public void run() {
		System.out.println("Thread is running");
	}
	}).start();

注意,这里使用了匿名类的写法,我们创建了一个Runnable接口的匿名类实例,并将它传给了Thread类的构造方法,最后调用Thread类的start()方法执行这个线程。
而如果直接将这段代码翻译成Kotlin版本,写法将如下所示:

Thread(object : Runnable {
	override fun run() {
		println("Thread is running")
	}
	}).start()

Kotlin中匿名类的写法和Java有一点区别,由于Kotlin完全舍弃了new关键字,因此创建匿名类实例的时候就不能再使用new了,而是改用了object关键字。这种写法虽然算不上复杂,但是
相比于Java的匿名类写法,并没有什么简化之处。但是别忘了,目前Thread类的构造方法是符合Java函数式API的使用条件的,下面我们就看看如何对代码进行精简,如下所示:

Thread(Runnable {
	println("Thread is running")
}).start()

这段代码明显简化了很多,既可以实现同样的功能,又不会造成任何歧义。因为Runnable类中只有一个待实现方法,即使这里没有显式地重写run()方法,Kotlin也能自动明白Runnable后面的Lambda表达式就是要在run()方法中实现的内容。
另外,如果一个Java方法的参数列表中有且仅有一个Java单抽象方法接口参数,我们还可以将接口名进行省略,这样代码就变得更加精简了:

Thread({
	println("Thread is running")
}).start()

不过到这里还没有结束,和之前Kotlin中函数式API的用法类似,当Lambda表达式是方法的最后一个参数时,可以将Lambda表达式移到方法括号的外面。同时,如果Lambda表达式还是方法的唯一一个参数,还可以将方法的括号省略,最终简化结果如下:

Thread {
	println("Thread is running")
}.start()

例子

在Kotlin中调用Java函数式API最经典的例子当属点击事件的接口了。
Android中有一个极为常用的点击事件接口OnClickListener,其定义如下:

public interface OnClickListener {
	void onClick(View v);
}

可以看到,这又是一个单抽象方法接口。假设现在我们拥有一个按钮button的实例,然后使用Java代码去注册这个按钮的点击事件,需要这么写:

button.setOnClickListener(new View.OnClickListener() {
	@Override
	public void onClick(View v) {
	}
});

而用Kotlin代码实现同样的功能,就可以使用函数式API的写法来对代码进行简化,结果如下:

button.setOnClickListener {
}

而最后这段给按钮注册点击事件的代码,将会在Android开发时经常用到。

你可能感兴趣的:(Anroid,android,kotlin,开发语言)