代码示例:
java.lambda.LambdaExpression
1 本质
lambda
表达式本质上是对匿名内部类实例的一种简化写法。
1.1 案例
有以下List<Integer>
对象:
List<Integer> list = Arrays.asList(1, 3, 5, 7, 9, 2, 4, 6, 8, 10);
在对List
进行从小大大排序时,会用到List#sort(Comparator)
方法,需要传递实现Comparator
接口的对象作为参数:
default void sort(Comparator<? super E> c) { // 省略方法体 }
可以想到有如下四种不同的代码编写的方式。
1、 创建Comparator
的实现类
根据需求,手动实现Comparator
接口:
public class AscComparator implements Comparator<Integer> { @Override public int compare(Integer o1, Integer o2) { return o1.compareTo(o2); } }
然后,创建AscComparator
实例,传给List#sort(Comparator)
方法:
Comparator<Integer> ascComparator = new AscComparator(); list.sort(ascComparator);
2、创建Comparator
的匿名对象
可以直接创建Comparator
的匿名对象,然后传给List#sort(Comparator)
方法:
Comparator<Integer> anonymousComparator = new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { return o1.compareTo(o2); } }; list.sort(anonymousComparator);
等价于:
list.sort(new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { return o1.compareTo(o2); } });
3、lambda
表达式
直接使用lambda
表达式:
list.sort((o1, o2) -> o1.compareTo(o2));
4、方法引用
使用方法引用(方法引用具体概念和使用可以查看相关文章):
list.sort(Integer::compare);
可以明显看出,使用lambda
表达式和方法引用极大提高了开发的速度,提升了代码的简洁性。
1.2 本质
实际上,lambda
表达式只是JVM提供的语法糖。在JVM执行过程中,会根据lambda
表达式的规则,动态创建出匿名的接口实现类对象。
lambda
表达式本质上是Java对象。
可以通过查看lambda
表达式的Class
对象和实例对象来证明这一点:
public class LambdaExpression { public void printConsumer(Consumer consumer) { System.out.println(consumer.getClass()); System.out.println(consumer.getClass().getInterfaces()[0]); System.out.println(consumer); } }
1、案例1
运行以下代码:
LambdaExpression lambdaObjPrinter = new LambdaExpression(); lambdaObjPrinter.printConsumer(o -> o.getClass()); lambdaObjPrinter.printConsumer(o -> o.getClass());
会有如下输出:
class lambda.LambdaExpression$$Lambda$1/2003749087 interface java.util.function.Consumer lambda.LambdaExpression$$Lambda$1/2003749087@41629346 class lambda.LambdaExpression$$Lambda$2/1078694789 interface java.util.function.Consumer lambda.LambdaExpression$$Lambda$2/1078694789@6d311334
- 这证明了执行过程中会根据
lambda
表达式动态生成函数式接口的实现类,并创建该实现类的实例。 - 同时,先后执行的2个
lambda
表达式,尽管格式相同,仍然动态生成了2个实现类。
查看编译后的.class
文件如下:
LambdaExpression lambdaObjPrinter = new LambdaExpression(); lambdaObjPrinter.printConsumer((o) -> { o.getClass(); }); lambdaObjPrinter.printConsumer((o) -> { o.getClass(); });
2、案例2
运行如下代码:
LambdaExpression lambdaObjPrinter = new LambdaExpression(); for (int i = 0; i < 2; i++) { lambdaObjPrinter.printConsumer(o -> o.getClass()); } System.out.println("============="); for (int i = 0; i < 2; i++) { lambdaObjPrinter.printConsumer(o -> o.getClass()); }
会发现有如下输出:
class lambda.LambdaExpression$$Lambda$1/2003749087 interface java.util.function.Consumer lambda.LambdaExpression$$Lambda$1/2003749087@41629346 class lambda.LambdaExpression$$Lambda$1/2003749087 interface java.util.function.Consumer lambda.LambdaExpression$$Lambda$1/2003749087@41629346 ============= class lambda.LambdaExpression$$Lambda$2/1078694789 interface java.util.function.Consumer lambda.LambdaExpression$$Lambda$2/1078694789@6d311334 class lambda.LambdaExpression$$Lambda$2/1078694789 interface java.util.function.Consumer lambda.LambdaExpression$$Lambda$2/1078694789@6d311334
- 说明在不同
for
循环中(while
等循环结果相同),只会动态生成1个实现类。
查看编译后的.class
文件如下:
LambdaExpression lambdaObjPrinter = new LambdaExpression(); int i; for(i = 0; i < 2; ++i) { lambdaObjPrinter.printConsumer((o) -> { o.getClass(); }); } System.out.println("============="); for(i = 0; i < 2; ++i) { lambdaObjPrinter.printConsumer((o) -> { o.getClass(); }); }
- 说明这不是编译器编译的结果,应该是JVM执行时对循环语句中
lambda
表达式的优化。
2 基本语法
lambda
表达式本质上是对函数式接口的匿名实现类实例的一种简化写法:方法格式和lambda
表达式格式一一对应。
对于执行逻辑而言,方法主要由两部分组成(没有返回值):形参和方法体。
lambda
表达式与之对应:
1、形参:(t1, t2[, ……])
,对应方法的形参(T1 t1, T2 t2[, ……])
2、箭头:->,固定
3、方法体:{},对应方法的方法体
2.1 分类
根据方法形参和返回值的不同组合,lambda表达式可以分成以下几类:
- 没有形参:
() -> { // 方法体 }
- 一个形参:
(t) -> { // 方法体 }
-
多个形参:
(t1, t2[, ……]) -> {
// 方法体
} -
没有返回值:
() -> { // 方法体 }
- 有返回值:
() -> { // 方法体 return something; }
根据形参个数的不同,形参部分可以有不同的写法:
1、没有形参或多个形参(超过1个),需要带()
:
() -> { // 方法体 } (t1, t2[, ……]) { // 方法体 }
2、一个形参,可以省略()
:
(t) -> { // 方法体 } t -> { // 方法体 }
根据方法体中代码行数的不同,方法体部分也有不同的写法:
1、一行代码,可以省略{}
(此时该行代码的return
和;
也必须省略):
() -> { System.out.println("Hello World!"); } () -> System.out.println("Hello World!") () -> { return "Hello World!" } () -> "Hello World!"
2、多行代码,不可以省略{}
:
() -> { System.out.println("Hello"); System.out.println("World!"); } () -> { System.out.println("Hello"); return "Hello World!" }
2.2 案例
- 定义函数式接口,模拟不同类型的
lambda
表达式:
public class FunctionInterface { interface AcceptEmpty { void accept(); } interface AcceptOne<T> { void accept(T t); } interface AcceptMore<T, E> { void accept(T t, E e); } interface ReturnVoid { void returnVoid(); } interface ReturnR<R> { R returnR(); } }
- 定义调用类,接收不同的
lambda
表达式:
/** * 调用函数式接口的服务类 * @param <T> 第一个形参类型 * @param <E> 第二个形参类型 * @param <R> 返回值类型 */ public class Service<T, E, R> { private T t; private E e; public Service(T t, E e) { this.t = t; this.e = e; } void acceptEmpty(FunctionInterface.AcceptEmpty acceptEmpty) { acceptEmpty.accept(); } void acceptOne(FunctionInterface.AcceptOne<T> acceptOne) { acceptOne.accept(this.t); } void acceptMore(FunctionInterface.AcceptMore<T, E> acceptMore) { acceptMore.accept(this.t, this.e); } void returnVoid(FunctionInterface.ReturnVoid returnVoid) { returnVoid.returnVoid(); } R returnR(FunctionInterface.ReturnR<R> returnR) { return returnR.returnR(); } }
- 创建服务类实例:
Service<Integer, Integer, String> service = new Service<>(1, 2);
1、没有形参
service.acceptEmpty(new FunctionInterface.AcceptEmpty() { @Override public void accept() { System.out.println("没有形参"); } }); service.acceptEmpty(() -> { System.out.println("没有形参"); }); service.acceptEmpty(() -> System.out.println("没有形参"));
2、一个形参
service.acceptOne(new FunctionInterface.AcceptOne<Integer>() { @Override public void accept(Integer t) { System.out.println(t); } }); service.acceptOne((t) -> System.out.println(t)); service.acceptOne(t -> System.out.println(t));
3、多个形参
service.acceptMore(new FunctionInterface.AcceptMore<Integer, Integer>() { @Override public void accept(Integer t, Integer e) { System.out.println(t); System.out.println(e); } }); service.acceptMore((t, e) -> { System.out.println(t); System.out.println(e); });
4、没有返回值
service.returnVoid(new FunctionInterface.ReturnVoid() { @Override public void returnVoid() { System.out.println("没有返回值"); } }); service.returnVoid(() -> System.out.println("没有返回值"));
5、有返回值
service.returnR(new FunctionInterface.ReturnR<String>() { @Override public String returnR() { return "3"; } }); service.returnR(() -> "3");
3 执行逻辑
lambda
表达式本质上是传递了一个动态生成的匿名对象,是一种假的函数式编程。
lambda
表达式形式上看起来很像是函数式编程:将一个函数当作形参传给方法。
实际上,lambda表达式只是Java的一个语法糖,它本质上仍然是一个普通的Java对象。
在执行的过程中,lambda表达式最终还是会被解析成匿名的接口实现类对象。
由于多态特性,在执行过程中,调用是外部传进来的实现类实例的代码。
在这个过程中,我们甚至可以将该匿名对象保存起来,便于后续多次调用。
- 定义一个函数式接口:
public interface Lambda<T, R> { R method(T t); }
- 定义调用类:
public class FakeFunctionalProgramming<T, R> { private T t; private Lambda<T, R> lambda; public void setT(T t) { this.t = t; } public void setLambda(Lambda<T, R> lambda) { this.lambda = lambda; } public void doSomeThing() { T t = before(); R r = lambda.method(t); after(r); } public T before() { return t; } public void after(R r) { System.out.println(r); } }
- 执行以下代码:
FakeFunctionalProgramming<String, String> ffp = new FakeFunctionalProgramming<>(); ffp.setT("Xianhuii"); ffp.setLambda((t) -> "Hello " + t + "!"); ffp.doSomeThing(); // Hello Xianhuii!
从上述结果可以看出,lambda
表达式的编程方式本质上是利用了多态的特性,同时又使用了模板方法模式:
- 调用处接收一个接口实例
Lambda<T, R>
作为形参。 - 执行
before()
方法,处理相对固定的前处理逻辑。 - 将执行过程中相关值作为形参传给
Lambda<T, R>
实例,进行特定处理。 - 接收
Lambda<T, R>
特定处理后的返回值。 - 执行
after()
方法,处理相对固定的后处理逻辑。
此时,我们应该能够透彻理解lambda
表达式中形参的来源,返回值的去向了。
借助Java多态特性,以及JVM动态生成匿名实现类实例的功能,lambda
表达式才表现得那么像是函数式编程。