1、初体验
目标:了解使用匿名内部类存在的问题,体验Lambda 匿名内部类存在的问题:当需要启动一个线程去完成任务时,通常会通过Runnable 接口来定义任务内容,并使用Thread 类来启动该线程。
1、传统写法
代码语言:javascript复制public class LambdaIntro01 {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("启动一个线程");
}
}).start();
}
}
代码分析:
由于面向对象的语法要求,首先创建一个Runnable 接口的匿名内部类对象来指定线程要执行的任务内容,再将其交给一个线程来启动。 对于Runnable 的匿名内部类用法,可以分析出几点内容:
- Thread 类需要Runnable 接口作为参数,其中的抽象run 方法是用来指定线程任务内容的核心
- 为了指定run 的方法体,不得不需要Runnable 接口的实现类
- 为了省去定义一个Runnable 实现类的麻烦,不得不使用匿名内部类
- 必须覆盖重写抽象run 方法,所以方法名称、方法参数、方法返回值不得不再写一遍,且不能写错
- 而实际上,似乎只有方法体才是关键所在。
2、lambda写法
代码语言:javascript复制Lambda是一个匿名函数,可以理解为一段可以传递的代码。
public class LambdaIntro01 {
public static void main(String[] args) {
new Thread(() ->{
System.out.println("启动一个线程");
}).start();
}
}
代码分析:
这段代码和刚才的执行效果是完全一样的,可以在JDK 8或更高的编译级别下通过。从代码的语义中可以看出:我们启动了一个线程,而线程任务的内容以一种更加简洁的形式被指定。我们只需要将要执行的代码放到一个Lambda表达式中,不需要定义类,不需要创建对象。
2、Lambda的标准格式
1、格式
Lambda省去面向对象的条条框框,Lambda的标准格式格式由3个部分组成:
代码语言:javascript复制(参数类型 参数名称) -> {
代码体;
}
格式说明:
(参数类型 参数名称):参数列表 {代码体;}:方法体 -> :箭头,分隔参数列表和方法体
2、无参数无返回值的Lambda
代码语言:javascript复制public interface PhoneStore {
public abstract void buy();
}
代码语言:javascript复制public class LambdaUse02 {
public static void main(String[] args) {
goStore(new PhoneStore() {
@Override
public void buy() {
System.out.println("买华为手机");
}
});
goStore(() -> System.out.println("买小米手机"));
}
public static void goStore(PhoneStore phoneStore){
phoneStore.buy();
}
}
3、有参数有返回值的Lambda
下面举例演示java.util.Comparator<T> 接口的使用场景代码,其中的抽象方法定义为:
代码语言:javascript复制public abstract int compare(T o1, T o2);
当需要对一个对象集合进行排序时, Collections.sort 方法需要一个Comparator 接口实例来指定排序的规则。
实体类
代码语言:javascript复制public class Person {
private String name;
private Integer age;
private Date birthday;
public Person(String name, Integer age, Date birthday) {
this.name = name;
this.age = age;
this.birthday = birthday;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
@Override
public String toString() {
return new StringJoiner(", ", Person.class.getSimpleName() "[", "]")
.add("name='" name "'")
.add("age=" age)
.add("birthday=" birthday)
.toString();
}
}
传统写法VS Lambda写法
传统写法
代码语言:javascript复制public class Lambda03 {
public static void main(String[] args) {
ArrayList<Person> persons = new ArrayList<>();
persons.add(new Person("刘德华", 58, new Date()));
persons.add(new Person("张学友", 58, new Date()));
persons.add(new Person("刘德华", 54, new Date()));
persons.add(new Person("黎明", 53, new Date()));
Collections.sort(persons, new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
return o1.getAge() - o2.getAge();
}
});
for (Person person : persons) {
System.out.println(person);
}
}
}
Lambda写法
代码语言:javascript复制public class Lambda03 {
public static void main(String[] args) {
ArrayList<Person> persons = new ArrayList<>();
persons.add(new Person("刘德华", 58, new Date()));
persons.add(new Person("张学友", 58, new Date()));
persons.add(new Person("刘德华", 54, new Date()));
persons.add(new Person("黎明", 53, new Date()));
Collections.sort(persons,(o1, o2)->{
return o1.getAge() - o2.getAge();
});
//另一种写法
Collections.sort(persons, Comparator.comparingInt(Person::getAge));
for (Person person : persons) {
System.out.println(person);
}
}
}
3、省略格式
在Lambda标准格式的基础上,使用省略写法的规则为:
- 小括号内参数的类型可以省略
- 如果小括号内有且仅有一个参数,则小括号可以省略
- 如果大括号内有且仅有一个语句,可以同时省略大括号、return关键字及语句分号
(int a) ->{
return new Person();
}
省略后
代码语言:javascript复制a -> new Person();
4、前提条件
Lambda的语法非常简洁,但是Lambda表达式不是随便使用的,使用时有几个条件要特别注意:
- 方法的参数或局部变量类型必须为接口才能使用Lambda
- 接口中有且仅有一个抽象方法
public interface Flyable {
public abstract void flying();
}
代码语言:javascript复制public class Lambda04 {
public static void main(String[] args) {
test(() ->{});
Flyable flyable = new Flyable() {
@Override
public void flying() {
}
};
Flyable flyable1 = () ->{
};
}
public static void test(Flyable flyable){
flyable.flying();
}
}
5、函数式接口
函数式接口在Java中是指:有且仅有一个抽象方法的接口。 函数式接口,即适用于函数式编程场景的接口。而Java中的函数式编程体现就是Lambda,所以函数式接口就是可以适用于Lambda使用的接口。只有确保接口中有且仅有一个抽象方法,Java中的Lambda才能顺利地进行推导。
FunctionalInterface注解
与@Override 注解的作用类似,Java 8中专门为函数式接口引入了一个新的注解:@FunctionalInterface 。该注解可用于一个接口的定义上:
代码语言:javascript复制@FunctionalInterface
public interface Operator {
void myMethod();
}
一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。不过,即使不使用该注解,只要满足函数式接口的定义,这仍然是一个函数式接口,使用起来都一样。
6、Lambda和匿名内部类对比
不同 | Lambda | 匿名内部类 |
---|---|---|
所需的类型 | 需要的类型必须是接口 | 需要的类型可以是类,抽象类,接口 |
抽象方法的数量 | 所需的接口只能有一个抽象方法 | 所需的接口中抽象方法的数量随意 |
实现原理 | 是在程序运行的时候动态生成class | 是在编译后会形成class |
总结:当接口中只有一个抽象方法时,建议使用Lambda表达式,其他其他情况还是需要使用匿名内部