使用匿名内部类存在的问题
当需要启动一个线程去完成任务时,通常会通过 Runnable 接口来定义任务内容,并使用 Thread 类来启动该线程。
传统写法, 代码如下:
public class LambdaTest1 {public static void main(String[] args) {new Thread(new Runnable() {@Overridepublic 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() {@Overridepublic 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>() {@Overridepublic 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>() {@Overridepublic 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表达式,其他其他情况还是需要使用匿名内部类