使用匿名内部类存在的问题
当需要启动一个线程去完成任务时,通常会通过
Runnable
接口来定义任务内容,并使用
Thread
类来启动该线程。
传统写法,
代码如下:
public class LambdaTest1 {
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 方法,所以方法名称、方法参数、方法返回值不得不再写一遍,且不能写错
- 而实际上,似乎只有方法体才是关键所在。
Lambda体验
Lambda
是一个
匿名函数
,可以理解为一段可以传递的代码。
Lambda
表达式写法,
代码如下:
借助Java 8
的全新语法,上述
Runnable
接口的匿名内部类写法可以通过更简单的
Lambda
表达式达到相同的效果
public class LambdaTest2 {
public static void main(String[] args) {
new Thread(() -> System.out.println("新线程任务执行!")).start(); // 启动线程
}
}
这段代码和刚才的执行效果是完全一样的,可以在
JDK 8
或更高的编译级别下通过。从代码的语义中可以看出:我们 启动了一个线程,而线程任务的内容以一种更加简洁的形式被指定。
我们只需要将要执行的代码放到一个
Lambda
表达式中,不需要定义类,不需要创建对象
Lambda的优点
一句话总结:简化匿名内部类的使用,语法更加简单
Lambda的标准格式
Lambda
省去面向对象的条条框框,
Lambda
的标准格式格式由
3
个部分
组成:
( 参数类型 参数名称 ) -> {代码体;}
格式说明:
- (参数类型 参数名称):参数列表
- {代码体;}:方法体
- -> :箭头,分隔参数列表和方法体
Lambda与方法的对比:
匿名内部类:
public void run() {
System.out.println("aa");
}
Lambda:
() -> System.out.println("bb!")
无参数无返回值的Lambda:
interface Swimmable {
public abstract void swimming();
}
package com.itheima.demo01lambda;
public class Demo02LambdaUse {
public static void main(String[] args) {
//调用匿名内部类的方法
goSwimming(new Swimmable() {
@Override
public void swimming() {
System.out.println("匿名内部类游泳");
}
});
//调用Lambda表达式来写
goSwimming(() -> {
System.out.println("Lambda游泳");
});
}
//定义一个方法,参数传一个Swimmable接口
public static void goSwimming(Swimmable s) {
s.swimming();
}
}
小结:以后我们看到方法的参数是接口就可以考虑使用Lambda表达式
我们可以这么认为:Lambda表达式就是对接口中的抽象方法的重写,但是接口必须是函数式接口,有且仅有一个抽象方法的接口。
有参数有返回值的Lambda:
下面举例演示
java.util.Comparator<T>
接口的使用场景代码,其中的抽象方法定义为:
- public abstract int compare(T o1, T o2);
当需要对一个对象集合进行排序时,
Collections.sort
方法需要一个
Comparator
接口实例来指定排序的规则。
传统写法
如果使用传统的代码对
ArrayList
集合进行排序,写法如下:
public class Person {
private String name;
private int age;
private int height;
// 省略其他
}
package com.itheima.demo01lambda;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
public class Demo03LambdaUse {
public static void main(String[] args) {
ArrayList<Person> persons = new ArrayList<>();
persons.add(new Person("刘德华", 58, 174));
persons.add(new Person("张学友", 58, 176));
persons.add(new Person("刘德华", 54, 171));
persons.add(new Person("黎明", 53, 178));
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);
}
}
}
这种做法在面向对象的思想中,似乎也是
“
理所当然
”
的。其中
Comparator
接口的实例(使用了匿名内部类)代表了“
按照年龄从小到大
”
的排序规则。
Lambda
写法 :
package com.itheima.demo01lambda;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
public class Demo03LambdaUse {
public static void main(String[] args) {
ArrayList<Person> persons = new ArrayList<>();
persons.add(new Person("刘德华", 58, 174));
persons.add(new Person("张学友", 58, 176));
persons.add(new Person("刘德华", 54, 171));
persons.add(new Person("黎明", 53, 178));
Collections.sort(persons, (o1, o2) -> {
return o1.getAge() - o2.getAge();
});
for (Person person : persons) {
System.out.println(person);
}
System.out.println("-----------------");
List<Integer> list = Arrays.asList(11, 22, 33, 44);
list.forEach(new Consumer<Integer>() {
@Override
public void accept(Integer integer) {
System.out.println(integer);
}
});
System.out.println("-----------------");
list.forEach((s) -> {
System.out.println(s);
});
}
}
Lambda省略格式
在
Lambda
标准格式的基础上,使用省略写法的规则为:
1.
小括号内参数的类型可以省略
2.
如果小括号内
有且仅有一个参数
,则小括号可以省略
3.
如果大括号内
有且仅有一个语句
,可以同时省略大括号、
return
关键字及语句分号
(int a) -> {
return new Person();
}
省略后:
a -> new Person()
Lambda使用的前提条件
Lambda
的语法非常简洁,但是
Lambda
表达式不是随便使用的,使用时有几个条件要特别注意:
1.
方法的参数或局部变量类型必须为接口才能使用
Lambda
2.
接口中有且仅有一个抽象方法
函数式接口
函数式接口在
Java
中是指:
有且仅有一个抽象方法的接口
。
函数式接口,即适用于函数式编程场景的接口。而
Java
中的函数式编程体现就是
Lambda
,所以函数式接口就是可以
适用于
Lambda
使用的接口。只有确保接口中有且仅有一个抽象方法,
Java
中的
Lambda
才能顺利地进行推导。
FunctionalInterface
注解
与
@Override
注解的作用类似,
Java 8
中专门为函数式接口引入了一个新的注
@FunctionalInterface
。该注解可用于一个接口的定义上:
@FunctionalInterface
public interface Operator {
void myMethod();
}
一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。不过,即使不使用该注解,只要满足函数式接口的定义,这仍然是一个函数式接口,使用起来都一样。
Lambda和匿名内部类对比
1. 所需的类型不一样
- 匿名内部类,需要的类型可以是类,抽象类,接口
- Lambda表达式,需要的类型必须是接口
2. 抽象方法的数量不一样
- 匿名内部类所需的接口中抽象方法的数量随意
- Lambda表达式所需的接口只能有一个抽象方法
3. 实现原理不同
- 匿名内部类是在编译后会形成class
- Lambda表达式是在程序运行的时候动态生成class
当接口中只有一个抽象方法时,建议使用Lambda表达式,其他其他情况还是需要使用匿名内部类