lambda expression的语法
语法: {Parameters ->Body}
举例:
val sum = { x: Int, y: Int -> x + y } //store a lambda expression in a variable.
println(sum(2, 5)) //treat this variable like a normal function.
与局部变量一样,lambda表达式中的参数可以自动推断出来。
如果一个lambda expression是一个函数的最后一个参数,那么可以将该lambda expression移到函数的小括号外面;
val people = listOf(Person("Alice", 29), Person("Bob", 31))
val names = people.joinToString(separator = " ",
transform = { p: Person -> p.name })
可以简化为:
people.joinToString(" ") { p: Person -> p.name }
如果一个lambda expression是一个函数的唯一参数,那么可以进一步的省去函数的小括号。
people.maxBy { p: Person -> p.age } //显式指定参数类型
people.maxBy { p -> p.age } //参数类型自动推断而出
也可以使用默认的参数名字it:
people.maxBy { it.age }
如果把lambda表达式赋值给一个变量,则没有推断可以参数类型的上下文,所以需要显式指定参数类型:
val getAge = { p: Person -> p.age }
people.maxBy(getAge)
lambda表达式并不仅限于一个表达式或一条语句,此时最后一个expression的值为lamada的结果。
fun main() {
val sum = { x: Int, y: Int ->
println("Computing the sum of $x and $y...")
x + y
var a = 10
}
println(sum(10, 20)) //kotlin.Unit
}
lamda优于匿名内部类
Don’t use anonymous classes for function objects unless you have to create instances of types that aren’t functional interfaces.
lambda表达式的局限性
- Lambda仅限于函数式接口。 如果要创建一个abstract class的实例,则只能使用anonymous class;
- 类似的,如果要创建具有多个抽象方法的接口的实例,也只能使用anonymous class;
- 不同于匿名类,lambda中this是“调用lambda表达式的对象”,而匿名内部类中的this表示的是“匿名内部类对象本身”。
举例:
package com.sample.java;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class JavaSample {
private static Runnable lambda1 = () -> {
System.out.println("lambda1");
//System.out.println("lambda1 this: " + this.getClass()); //'this' cannot be referenced from a static context.
};
private static Runnable runnable = new Runnable() {
@Override
public void run() {
//runnable this: com.sample.java.JavaSample$1
System.out.println("runnable this: " + this.getClass());
}
};
public static void main(String[] args) {
new Thread(lambda1).start();
new Thread(runnable).start();
new InnerClass().testLambda();
}
private static class InnerClass {
private Comparator<String> lambda2 = (String s1, String s2) -> {
System.out.println("lambda2 this: " + this.getClass()); //com.sample.java.JavaSample$InnerClass
return Integer.compare(s1.length(), s2.length());
};
private void testLambda() {
List<String> list = Arrays.asList("hello", "world");
Collections.sort(list, lambda2);
}
}
}
在作用域内访问变量
Lameda表达式访问外部变量时,不要求是final类型,这意味着可以在lambda表达式中修改外部变量,我们称之为被lambda表达式captured。举例:
package com.example.kotlin.ch05
fun printlnProblemCounts(response: Collection<String>) {
var clientErrors = 0
var serverErrors = 0
response.forEach {
if (it.startsWith('4')) {
clientErrors++
} else if (it.startsWith('5')) {
serverErrors++
}
}
println("$clientErrors client errors, $serverErrors server errors")
}
fun main() {
val responses = listOf(
"200 OK", "418 I'm a teapot",
"500 Internal Server Error"
)
printlnProblemCounts(responses)
}
注意:默认情况下,如果一个local variable被lambda表达式captured的话,使用该variable的代码可以先暂存起来、稍后再执行。
当捕获一个final variable时,它的值与lambda的代码保存在一起;
当捕获一个non-final variable时,它的值被封装在一个特殊的wrapper当中,这个wrapper的引用与lambda的代码保存在一起。
Member references
Member references即对function的引用,使用::操作符可以把function转化成value:
val getAge = Person::age
people.maxBy(Person::age)
对顶层函数的引用:
fun salute() = println("Salute!")
run(::salute)
Constructor reference.
使用constructor reference可以将创建一个实例的动作保存在value中。
data class Person(val name: String, val age: Int)
//The action of creating an instance of “Person” is saved as a value.
val createPerson = ::Person
val p = createPerson("Alice", 29)
println(p) //Person(name=Alice, age=29)
引用extension functions也是类似:
fun Person.isAdult() = age >= 21
val predicate = Person::isAdult
函数式接口(functional interfaces)
仅有一个抽象方法的接口称之为函数式接口(functional interfaces),又称为SAM(single abstract method)接口。比如Java中Runnable和Callable这种。这种接口的实例称之为函数式对象(function objects)。
任何期待functional interface类型参数的地方,都可以给它传递一个lambda表达式。
举例:
void postponeComputation(int delay, Runnable computation);
使用kotlin调用时:
postponeComputation(1000) { println(42) }
编译器会自动将lambda转化为"实现了Runnable接口的匿名类的实例"。
postponeComputation(1000, object : Runnable {
override fun run() {
println(42)
}
})
Collections中的函数式APIs
-
filter
filter会从collections中移除不想要的元素,但是并不改变元素本身。
val list = listOf(1, 2, 3, 4, 5)
val evenList = list.filter { it % 2 == 0 }
println(evenList) //[2, 4]
-
map
map函数应用指定的function到collections中的每一个元素,并收集结果到一个新的collection中。
val list = listOf(1, 2, 3, 4, 5)
val mapList = list.map { it * it }
println(mapList) //[1, 4, 9, 16, 25]
如果只想取出Person列表中Person的名字,那么可以这样写:
val people = listOf(Person("Alice", 29), Person("Bob", 31))
println(people.map { it.name })
使用member references的形式:
people.map(Person::name)
也可以使用调用链,比如打印年龄大于30岁的人的名字:
people.filter { it.age > 30 }.map(Person::name)
找到年龄最大的那个人:
val maxAge = people.maxBy { it.age ?: 0 }?.age"
println(people.filter { it.age == maxAge })
"all", "any", "count"和"find":在collection上应用一个predicate
"group by" : 将一个list转化为一组maps
val people = listOf(
Person("Alice", 31),
Person("Bob", 29), Person("Carol", 31)
)
println(people.groupBy { it.age })
output:
{31=[Person(name=Alice, age=31), Person(name=Carol, age=31)], 29=[Person(name=Bob, age=29)]}
如图:
返回类型是:Map<Int, List<Person>>
看另外一个例子,通过String的首字符进行分组:
val list = listOf("a", "ab", "b")
println(list.groupBy(String::first))
输出:
{a=[a, ab], b=[b]}
flatMap和flatten:处理嵌套的Collections中的元素
flatMap主要做了两件事情:
- 将Collection中的每一个元素应用function argument指定的函数,转换成一个list,这一步称之为map;
- 将这些lists压缩成一个list,这一步称之为flatten。
举例:
class Book(val title: String, val authors: List<String>)
fun main() {
val strings = listOf("abc", "def")
println(strings.flatMap { it.toList() }) //[a, b, c, d, e, f]
val books = listOf(
Book("Thursday Next", listOf("Jasper Fforde")),
Book("Mort", listOf("Terry Pratchett")),
Book("Good Omens", listOf("Terry Pratchett", "Neil Gaiman"))
)
println(books.flatMap { it.authors }) //[Jasper Fforde, Terry Pratchett, Terry Pratchett, Neil Gaiman]
}
如图: