1. Lambda表达式引入
当需要启动一个线程去完成任务时,通常会通过 java.lang.Runnable
接口来定义任务内容,并使用 java.lang.Thread
类来启动该线程。
传统写法,代码如下:
public class ThreadDemo {
public static void main(String[] args) {
new Thread(new Runnable() {
//run方法用于指定线程的具体任务内容
@Override
public void run() {
System.out.println("多线程任务执行!");
}
}).start();
}
}
本着”一切皆对象”的思想,这种做法是无可厚非的:首先创建一个 Runnable
接口的匿名内部类对象来指定任务内容,再将其交给一个线程来启动。
代码分析:
对于 Runnable
的匿名内部类用法,可以分析出几点内容:
Thread
类需要Runnable
接口作为参数,Runnable
接口的抽象run
方法是用来指定线程任务内容的;- 为了指定
run
的方法体,不得不需要Runnable
接口 的实现类; - 为了省去定义一个
Runnable
接口实现类的麻烦,不得不使用匿名内部类; - 必须覆盖重写抽象
run
方法,所以方法名称、方法参数、方法返回值不得不再写一遍,且不能写错; - 而实际上,似乎只有方法体才是关键所在。
Lambda表达式写法,代码如下:
Lambda 表达式是在Java 8 语言中引入的新特性,借助 Java 8 新语法,上述 Runnable
接口的匿名内部类写法可以通过更简单的 Lambda表达式 达到等效:
public class LambdaRunnableDemo {
public static void main(String[] args) {
new Thread(() -> System.out.println("多线程任务执行!")).start();
}
}
这段代码和刚才的执行效果是完全一样的,可以在 JDK 8 或更高的编译级别下通过。从代码的语义中可以看出:我们启动了一个线程,而线程任务的内容以一种更加简洁的形式被指定。
2. Lambda表达式的格式
标准格式:
Lambda省去面向对象的条条框框,格式由3个部分组成:
- 一些参数
- 一个箭头
- 一段代码
Lambda表达式的标准格式为:
(形参列表) -> {Lambda体}
格式说明:
- 小括号内的语法与传统方法参数列表一致:无参数则留空;多个参数则用逗号分隔。
->
是新引入的操作符,代表指向动作。- 大括号内的语法与传统方法体要求基本一致。
匿名内部类与lambda表达式对比
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("多线程任务执行!");
}
}).start();
仔细分析该代码中,Runnable
接口只有一个 run
方法的定义:
public abstract void run();
即制定了一种做事情的方案(其实就是一个方法):
- 无参数:不需要任何条件即可执行该方案。
- 无返回值:该方案不产生任何结果。
- 代码块(方法体):该方案的具体执行步骤。
同样的语义体现在Lambda
语法中更加简单:
() -> System.out.println("多线程任务执行!")
- 前面的一对小括号即
run
方法的参数(无),代表不需要任何条件; - 中间的一个箭头代表将前面的参数传递给后面的代码;
- 后面的输出语句即业务逻辑代码。
下面举例演示java.util.Comparator<T>
接口的使用场景,接口的抽象方法为:public abstract int compare(T o1, T o2);
当需要对一个对象数组进行排序时,Arrays.sort
方法需要一个 Comparator
接口的实现类对象来指定排序的规则。假设有一个Student
类,含有String name
和int age
两个成员变量:
public class Student {
private String name;
private int age;
// 省略构造器、toString方法与Getter Setter
}
传统写法
如果使用传统的代码对Person[]
数组进行排序,写法如下:
public class ComparatorDemo {
public static void main(String[] args) {
// 本来年龄乱序的对象数组
Student[] array = { new Student("lily", 19),
new Student("jack", 18),
new Student("mark", 20) };
// 匿名内部类
Comparator<Student> com = new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
// 指定排序规则,按年龄升序排序
return o1.getAge() - o2.getAge();
}
};
Arrays.sort(array, com); // 第二个参数为排序规则,即Comparator接口的实现类对象
for (Student student : array) {
System.out.println(student);
}
}
}
这种做法在面向对象的思想中,似乎也是“理所当然”的。其中Comparator
接口的实现类对象(使用了匿名内部类)代表了“按照年龄从小到大”的排序规则。
代码分析
下面我们来搞清楚上述代码真正要做什么事情。
- 为了排序,
Arrays.sort
方法需要排序规则,即Comparator
接口的实现类对象,抽象方法compare
是关键; - 为了指定
compare
的方法体,不得不需要Comparator
接口的实现类; - 为了省去定义一个
Comparator
接口实现类的麻烦,不得不使用匿名内部类; - 必须覆盖重写抽象
compare
方法,所以方法名称、方法参数、方法返回值不得不再写一遍,且不能写错; - 实际上,只有参数和方法体才是关键。
Lambda写法
public class ComparatorLambdaDemo {
public static void main(String[] args) {
Student[] array = { new Student("lily", 19),
new Student("jack", 18),
new Student("mark", 20) };
// 匿名内部类 简化为 lambda表达式:隐含地new了一个Comparator接口的实现类对象,并实现了compare方法
Comparator<Student> com = (Student o1, Student o2) -> {
// 指定排序规则,按升序排序
return o1.getAge() - o2.getAge();
};
Arrays.sort(array, com);
for (Student student : array) {
System.out.println(student);
}
}
}
省略格式:
省略规则
在Lambda标准格式的基础上,使用省略写法的规则为:
-
(形参列表)
内参数的类型可以省略; -
如果
(形参列表)
内有且仅有一个参数,则 () 可以省略,仅保留参数名; -
如果
{lambda体}
内有且仅有一条语句,则无论是否有返回值,都可以省略大括号{}、return关键字及语句分号。//返回值为5 () -> 5
可推导即可省略
Lambda强调的是“做什么”而不是“怎么做”,所以凡是可以推导得知的信息,都可以省略。例如上例还可以使用Lambda的省略写法:
Arrays.sort(array, (o1, o2) -> o1.getAge() - o2.getAge());
3. Lambda表达式的前提条件
Lambda的语法非常简洁,去除了面向对象复杂的束缚。但有使用的前提条件:
-
必须有接口,且要求接口中有且仅有一个抽象方法。
无论是JDK内置的Runnable
、Comparator
接口还是自定义的接口,只有当接口中有且仅有一个抽象方法时,才可以使用Lambda表达式。 -
必须有接口类型的变量或接口作为方法形参。
Lambda表达式是用来给接口类型的变量或方法形参赋值的。也就是变量类型或方法形参必须为Lambda表达式对应的接口类型,才能使用Lambda表达式作为该接口的实现类对象。
代码示例:
public interface MyInterface { void run(String str); }
public class LambdaTest { public static void main(String[] args) { //lambda表达式替代匿名内部类,并赋值为接口类型的变量 MyInterface myInterface = str -> System.out.println(str + ", run方法执行了"); //执行run方法 myInterface.run("hello"); } }
4. Lambda表达式的本质
有且仅有一个抽象方法的接口,称为 函数式接口(该接口可以包含非抽象方法)。
表面上看Lambda表达式是一个匿名函数,其本质是:函数式接口 的 实现类的对象,用于实现 函数式接口 的 抽象方法。
5. Lambda表达式和匿名内部类的区别
- 所需的类型不同
匿名内部类,需要的类型可以是普通类、抽象类、接口
Lambda表达式,需要的类型必须是接口
- 抽象方法的数量同
匿名内部类所需的接口中抽象方法的数量不限
Lambda表达式所需的接口只能有一个抽象方法
当接口中只有一个抽象方法时,建议使用Lambda表达式,其他其他情况还是需要使用匿名内部类。