Java lambda表达式基本使用 - Xianuii
source link: https://www.cnblogs.com/Xianhuii/p/16927003.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
代码示例:
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
表达式才表现得那么像是函数式编程。
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK