Lambda编程

参考链接

《知乎:Lambda 表达式》
《Kotlin实战》
《Kotlin中文文档》

Lambda表达式本质上就是可以传递给其他函数一小段代码。

有了Lambda可以轻松地把通用代码结构抽取成函数库,Kotlin标准库大量使用了它。

什么是Lambda?

我们知道,对于一个Java变量,我们可以赋给其一个“值”。如果你想把“一块代码”赋给一个Java变量,应该怎么做呢?

在Java 8之前,这个是做不到的。但是Java 8问世之后,利用Lambda特性,就可以做到了。

1
2
3
aBlockOfCode = public void doSomeShit(String s){
System.out.println(s);
}

简便写法

1、public和函数名是多余的,因为已经赋值给了aBlockOfCode.
2、编译器可以自动判断参数类型和返回类型。
3、只有一行可以省略大括号。

1
aBlockOfCode = (s) -> System.out.println(s);

这样我们就将一个代码块成功赋值给了变量,那么aBlockOfCode的类型是什么呢?

在Java8中,所有Lambda类型都是一个接口,而等号右边则是接口的实现。

一个接口函数需要被实现的接口类型,我们叫它“函数式接口”,为了避免后来的人在这个接口中增加接口函数导致其有多个接口函数需要被实现,变成”非函数接口”,我们可以在这个上面加上一个声明@FunctionalInterface, 这样别人就无法在里面添加新的接口函数了

1
2
3
4
@FunctionalInterface
interface MyLambdaInterface{
void doSomeShit(String s);
}

这样,我们就得到了一个完整的Lambda表达式声明:

1
MyLambdaInterface aBlockOfCode = (s) -> System.out.println(s);

Lambda表达式有什么作用?

使代码变的简洁

Java7实现过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static void enact(MyLambdaInterface myLambda, String s){
myLambda.doSomeShit(s);
}

public class MyInterfaceImpl implements MyLambdaInterface{

@Override
public void doSomeShit(String s){
System.out.println(s);
}
}

MyLambdaInterface anInterfaceImpl = new MyInterfaceImpl();

enact(anInterfaceImpl, "Hello World");

Java8实现过程:

1
enact(s -> System.out.println(s), "Hello World");

有些情况下,这个接口实现只需要用到一次。传统的Java 7必须要求你定义一个“污染环境”的接口实现MyInterfaceImpl,而相较之下Java 8的Lambda, 就显得干净很多。

另外

在kotlin中可以像Java8中那样使用Lambda.

1
button.setOnclickListener{  /*点击后处理*/ }

Lambda和集合

需求:找出列表中年龄最大的那个人

1
2
3
4
5
6
7
8
9
10
11
12
13
data class Person(val name: String, val age: Int)

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)
}

Lambda实现

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

people.maxBy({ it.age })

maxBy函数可以在任何集合上调用,而且只有一个实参(一个函数)指定比较那个值来找到最大元素。

花括号中的{it.age}就是实现了这个逻辑的Lambda.

Kotlin中Lambda的语法

1
{x: Int, y: Int -> x + y}

Kotlin的lambda表达式始终使用花括号包围,箭头将实参和函数体隔开。

1
2
3
4
5
6
7
8
9
10
val sum = {x: Int, y: Int -> x+y}
println(sum(1, 2))

//上下对比

interface LambdaSumInterface{
int sum(int x, int y);
}
LambdaSumInterface sumInterface = (x, y) -> x+y;
System.out.println(suminterface.sum(1, 2))

上面我们有一个需求:找出列表中年龄最大的那个人,简化过程如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
people.maxBy({p: Person -> p.age})
|
//kotlin约定:lambda表达式调用的最后一个实参可以放到括号外面
|
people.maxBy(){p: Person -> p.age}
|
//kotlin约定:函数只有唯一实参时可以去掉括号
|
people.maxBy{p: Person -> p.age}
|
//参数类型可以省略
|
people.maxBy{p -> p.age}
|
//只有一个参数,默认参数名称it代替命名参数
|
people.maxBy{it.age}

Lambda结合forEach和函数接口包

需求:遍历集合,找出名字中LastName以”Z”开头的FirstName

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public class MainActivity extends AppCompatActivity {

class Person{
private String firstName;
private String lastName;
private int age;
public Person(String fname, String lname, int age){
firstName = fname;
lastName = lname;
this.age = age;
}

public String getFirstName() {
return firstName;
}

public String getLastName() {
return lastName;
}

public int getAge() {
return age;
}
}

@SuppressLint("NewApi")
public static void checkAndExecute(List<Person> personList,
Predicate<Person> predicate,
Consumer<Person> consumer){
for(Person p : personList){
if(predicate.test(p)){
consumer.accept(p);
}
}
}

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

List<Person> guiltyPersons = Arrays.asList(
new Person("YiXing", "Zhao", 25),
new Person("XiaoHong", "Li", 23),
new Person("DaLi", "Wang", 22)
);
checkAndExecute(guiltyPersons,
p -> p.getLastName().startsWith("Z"),
d -> System.out.println(d.getFirstName()));
}
}

在Java8中有一个函数式接口包,里面定义了大量可能用到的函数式接口 java.util.function

使用iterable自带的forEach来替代。

1
2
3
4
5
6
@SuppressLint("NewApi")
public static void checkAndExecute(List<Person> personList,
Predicate<Person> predicate,
Consumer<Person> consumer){
personList.forEach(p -> {if(predicate.test(p)) consumer.accept(p);});
}

利用stream()替代静态函数

1
2
3
4
5
6
7
8
9
List<Person> guiltyPersons = Arrays.asList(
new Person("YiXing", "Zhao", 25),
new Person("XiaoHong", "Li", 23),
new Person("DaLi", "Wang", 22)
);

guiltyPersons.stream()
.filter(p -> p.getLastName().startsWith("Z"))
.forEach(p -> System.out.println(p.getFirstName()));

kotlin作用域中访问变量

和Java中不同的是,在Kotlin中不仅仅可以访问非final变量,也可以修改这些变量。

1
2
3
4
5
6
7
8
9
10
11
fun testFunction(messages: Collections<String>, prefix: String){
var clientErrNum = 0
var serverErrNum = 0
message.forEach{
if(it.startWith("4")){
clientErrNum++
}else if(it.startWith("5")){
serverErrNum++
}
}
}

成员引用

1
val getAge = Person::age

这种表达式称为成员引用,可以引用一个属性或者一个方法,注意方法不能带括号。

等价于

1
val getAge = {p: Person -> p.age}

我们知道Lambda可以将代码块作为参数传递给函数,如果要传递一个被定义的函数可以使用引用。

还可以引用顶层函数

1
2
3
fun salute() = println("Salute!")

>>> run(::salute)

也可以用构造方法引用

1
2
3
4
5
data class Person(val name: String, val age: Int)

val createPerson = ::Person
val p = createPerson("Alice", 26)
println(p)

还可以用同样的方式引用扩展函数

1
2
fun Person.isAdult() = age >= 21
val predicate = Person::isAdult