Lambda 练习


练习: 使用 Lambda 标准格式 (无参无返回)

题目

给定一个厨子 Cook 接口, 内含唯一的抽象方法 makeFood, 且无参数, 无返回值. 如下:

public class Test {
public static void main(String[] args) {
// TODO 请在此使用Lambda [标准格式] 调用invokeCook方法
}
private static void invokeCook(Cook cook) {
cook.makeFood();
}
}

解答

public static void main(String[] args) {
// TODO 请在此使用Lambda[标准格式]调用invokeCook方法
invokeCook(() ->{
System.out.println("吃饭了!");
});
}
注: 小括号代表 Cook 接口 makeFood 抽象方法的参数为空, 大括号代表 makeFood 的方法体.

Lambda 的参数和返回值

需求: 使用数组存储多个 Person 对象, 对数组中的 Person 对象使用 Arrays 的 sort 方法通过年龄进行升序排序.

下面举例演示​​java.util.Comparator<T>​​接口的使用场景代码, 其中的抽象方法定义为:

public abstract int compare(T o1, T o2);

当需要对一个对象数组进行排序时, Array.sort 方法需要一个 Comparator 接口实例来指定排序的规则. 假设有一个 Person 类, 含有 String name 和 int age 两个变量.

public class Person {
private String name;
private int age;
// 构造
Person(String name, int age){
this.name = name;
this.age = age;
}

public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}

传统写法

如果使用传统的代码对 Person[] 数组进行排序, 写法如下:

import java.util.Arrays;
import java.util.Comparator;
public class Test57 {
public static void main(String[] args) {
// 本来年龄乱序的对象数组
Person[] array = {
new Person("大白", 19),
new Person("小小白", 17),
new Person("小白", 18)
};
// 匿名内部类
Comparator<Person> comp = new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
return o1.getAge() - o2.getAge();
}
};
Arrays.sort(array,comp); // 第二个参数为排序规则, 即Comparator接口实例
System.out.println(Arrays.toString(array));
}
}
输出结果:
[Person{name='小小白', age=17}, Person{name='小白', age=18}, Person{name='大白', age=19}]

这种做法在面向对象的思想中, 似乎也是 “理所当然” 的. 其中 Comparator 接口的实例 (使用了匿名内部类) 代表了 “按照年龄从小到大” 的排序规则.

代码分析

下面我们来搞清楚上述代码真正要做什么事情.


  • 为了排序, Arrays.sort 方法需要排序规则. 即 Comparator 接口的实例, 抽象方法 compare 是关键
  • 为了指定 compare 的方法体, 不得不需要 Comparator 接口的实现类
  • 为了省去定义一个 ComparatorImpl 实现类的麻烦, 不得不使用匿名内部类
  • 必须覆盖重写抽象 compare 方法, 所以方法名称, 方法参数, 方法返回值不得不再写一遍, 其不能写错
  • 实际上, 只有参数和方法体才是关键

Lambda 写法

import java.util.Arrays;
public class Test58 {
public static void main(String[] args) {
// 本来年龄乱序的对象数组
Person[] array = {
new Person("大白", 19),
new Person("小小白", 17),
new Person("小白", 18)
};
Arrays.sort(array,(Person o1, Person o2)-> {
return o1.getAge() - o2.getAge();
});
System.out.println(Arrays.toString(array));
}
}
输出结果:
[Person{name='小小白', age=17}, Person{name='小白', age=18}, Person{name='大白', age=19}]

练习: 使用 Lambda 标准格式 (有参有返回)

题目

给一个计算器 Calculator 接口, 内含抽象方法 calc 可以将两个 int 数字相加得到和值:

public interface Calculator {
int calc(int a, int b);
}

在下面的代码中, 请使用 Lambda 的标准格式调用 invokeCalc 方法, 完成 120 和 130 相加计算.

public class Test {
public static void main(String[] args) {
// TODO 请在此使用 Lambda [标准格式] 调用 invokeCalc 方法来计算120+130的相加计算
invokeCalc(120,130,(int a, int b)->{
return a + b;
});
}
private static void invokeCalc(int a, int b, Calculator calculator) {
int result = calculator.calc(a, b);
System.out.println("结果是:" + result);
}
}
输出结果:
结果是:250

注: 小括号代表 Calculator 接口 calc 抽象方法的参数, 大括号代表 clac 的方法体.

Lambda 省略格式

可推导可省略

Lambda 强调的是 “做什么” 而不是 “怎么做”. 所以凡是可以根据上下文推导得知的信息, 都可以省略. 例如上例还可以使用 Lambda 的省略写法:

public class Test {
public static void main(String[] args) {
invokeCalc(120,130,(a,b) -> a + b);
}
private static void invokeCalc(int a, int b, Calculator calculator) {
int result = calculator.calc(a, b);
System.out.println("结果是:" + result);
}
}

省略规则

在 Lambda 标准格式的基础上, 使用省略写法的规则为:


  1. 小括号内参数的类型可以省略
  2. 如果小括号内有且仅有一个参数, 则小括号可以省略
  3. 如果大括号内有且仅有一个语句, 则无论是否有返回值, 都可以省略大括号, return 等关键字及语句分号

注: 掌握这些省略规则后请对应地回顾本章开头的多线程案例.

练习: 使用 Lambda 省略格式

题目

仍然使用前文含唯一 makeFood 抽象方法的厨子 Cook 接口, 在下面的代码中, 请使用 Lambda 的省略格式调用 invokeCook 方法, 打印输出 “吃饭了啦!” 字样.

public class Test {
public static void main(String[] args) {
// TODO 请在此使用Lambda [标准格式] 调用invokeCook方法
}
private static void invokeCook(Cook cook) {
cook.makeFood();
}
}

解答

public class Test61 {
public static void main(String[] args) {
// TODO 请在此使用Lambda [标准格式] 调用invokeCook方法
invokeCook(() -> System.out.println("吃饭了!"));
}
private static void invokeCook(Cook cook) {
cook.makeFood();
}
}
输出结果:
吃饭了!

Lambda 的使用前提

Lambda 的语法非常简洁, 完全没有面向对象复杂的束缚. 但是使用时有几个问题需要特别注意:


  1. 使用 Lambda 必须有接口, 且要求接口中有且仅有一个抽象方法. 无论是 JDK 内置的 Runnable, Comparator 接口还是自定义的接口, 只有当接口中的抽象方法存在且唯一时, 才可以使用 Lambda
  2. 使用 Lambda 必须具有上下文推断. 也就是方法的采纳数或局部变量类型必须为符合 Lambda 要求的接口类型, 才能使用 Lambda 作为该接口的实例

注: 有且仅有一个抽象方法的接口, 称为 “函数接口”.