一、背景
Java 8 的 Lambda 表达式已经不再是“新特性”。
现在很多人工作中会使用 Lambda 表达式。
但是,你是否真正理解 Lambda 表达式的底层原理?
本文给出自己的理解,希望对大家有帮助。
二、分析
下面是一段非常简单的代码,其中用到了 Stream
。
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class ListDemo {
public static void main(String[] args) {
List<DogDO> dogs = new ArrayList<>();
List<String> tom = dogs.stream().filter(dog -> dog.getName().startsWith("tom")).map(dog -> dog.getName().toLowerCase()).collect(Collectors.toList());
System.out.println(tom);
}
}
我们使用 Jclasslib
插件(《那些相见恨晚的 IDEA插件》 中有介绍),查看字节码:
大家也可以自己使用 javac 和 javap 指令去在命令行执行。
如:
javac ListDemo.java
javap -p -s -c -v -l ListDemo
我们可以看到多出一个内部类和 BootstrapMethods
lambda$main$0
0 aload_0
1 ldc #20 <tom>
3 invokevirtual #21 <java/lang/String.startsWith : (Ljava/lang/String;)Z>
6 ireturn
相当于:
private static boolean lambda$main$0(String name){
return name.startsWith("tom");
}
lambda$main$1
0 aload_0
1 invokevirtual #19 <java/lang/String.toLowerCase : ()Ljava/lang/String;>
4 areturn
相当于
private static String lambda$main$1(String name){
return name.toLowerCase();
}
通过上述简单分析就可以看出来,本质上 lambda 表达式最终会被编译为私有静态方法。
main 方法
0 new #2 <java/util/ArrayList>
3 dup
4 invokespecial #3 <java/util/ArrayList.<init> : ()V>
7 astore_1
8 aload_1
9 ldc #4 <jam>
11 invokeinterface #5 <java/util/List.add : (Ljava/lang/Object;)Z> count 2
16 pop
17 aload_1
18 ldc #6 <tom cat>
20 invokeinterface #5 <java/util/List.add : (Ljava/lang/Object;)Z> count 2
25 pop
26 aload_1
27 ldc #7 <tom jetty>
29 invokeinterface #5 <java/util/List.add : (Ljava/lang/Object;)Z> count 2
34 pop
35 aload_1
36 ldc #8 <gom jetty>
38 invokeinterface #5 <java/util/List.add : (Ljava/lang/Object;)Z> count 2
43 pop
44 aload_1
45 invokeinterface #9 <java/util/List.stream : ()Ljava/util/stream/Stream;> count 1
50 invokedynamic #10 <test, BootstrapMethods #0>
55 invokeinterface #11 <java/util/stream/Stream.filter : (Ljava/util/function/Predicate;)Ljava/util/stream/Stream;> count 2
60 invokedynamic #12 <apply, BootstrapMethods #1>
65 invokeinterface #13 <java/util/stream/Stream.map : (Ljava/util/function/Function;)Ljava/util/stream/Stream;> count 2
70 invokestatic #14 <java/util/stream/Collectors.toList : ()Ljava/util/stream/Collector;>
73 invokeinterface #15 <java/util/stream/Stream.collect : (Ljava/util/stream/Collector;)Ljava/lang/Object;> count 2
78 checkcast #16 <java/util/List>
81 astore_2
82 getstatic #17 <java/lang/System.out : Ljava/io/PrintStream;>
85 aload_2
86 invokevirtual #18 <java/io/PrintStream.println : (Ljava/lang/Object;)V>
89 return
通过 invokedynamic
指令执行动态方法调用。
如
50 invokedynamic #10 <test, BootstrapMethods #0>
我们可以直接在插件上,点击命令跳转到对应官方说明文档中:
https://docs.oracle.com/javase/specs/jvms/se16/html/jvms-6.html#jvms-6.5.invokedynamic
从 JVM 文档 可以看到 该方法是为了实现动态调用计算,它通过常量池重点额符号引用转为动态计算调用,其中 CallSite 实例就是目标方法调用实例。
可以在插件里一直跟下去
invokedynamic #10 中的常量池中的 #10 为下面的内容:
其中 BootstrapMethods # 0
对应
可以看到这是对 java.lang.invoke.LambdaMetafactory#metafactory
的调用,返回值是 java.lang.invoke.CallSite 对象,这个对象代表了真正执行的目标方法调用。
/**
* Facilitates the creation of simple "function objects" that implement one
* or more interfaces by delegation to a provided {@link MethodHandle},
* after appropriate type adaptation and partial evaluation of arguments.
* Typically used as a <em>bootstrap method</em> for {@code invokedynamic}
* call sites, to support the <em>lambda expression</em> and <em>method
* reference expression</em> features of the Java Programming Language.
*
* <p>This is the standard, streamlined metafactory; additional flexibility
* is provided by {@link #altMetafactory(MethodHandles.Lookup, String, MethodType, Object...)}.
* A general description of the behavior of this method is provided
* {@link LambdaMetafactory above}.
*
* <p>When the target of the {@code CallSite} returned from this method is
* invoked, the resulting function objects are instances of a class which
* implements the interface named by the return type of {@code invokedType},
* declares a method with the name given by {@code invokedName} and the
* signature given by {@code samMethodType}. It may also override additional
* methods from {@code Object}.
*
* @param caller Represents a lookup context with the accessibility
* privileges of the caller. Specifically, the lookup context
* must have
* <a href="MethodHandles.Lookup.html#privacc">private access</a>
* privileges.
* When used with {@code invokedynamic}, this is stacked
* automatically by the VM.
* @param invokedName The name of the method to implement. When used with
* {@code invokedynamic}, this is provided by the
* {@code NameAndType} of the {@code InvokeDynamic}
* structure and is stacked automatically by the VM.
* @param invokedType The expected signature of the {@code CallSite}. The
* parameter types represent the types of capture variables;
* the return type is the interface to implement. When
* used with {@code invokedynamic}, this is provided by
* the {@code NameAndType} of the {@code InvokeDynamic}
* structure and is stacked automatically by the VM.
* In the event that the implementation method is an
* instance method and this signature has any parameters,
* the first parameter in the invocation signature must
* correspond to the receiver.
* @param samMethodType Signature and return type of method to be implemented
* by the function object.
* @param implMethod A direct method handle describing the implementation
* method which should be called (with suitable adaptation
* of argument types, return types, and with captured
* arguments prepended to the invocation arguments) at
* invocation time.
* @param instantiatedMethodType The signature and return type that should
* be enforced dynamically at invocation time.
* This may be the same as {@code samMethodType},
* or may be a specialization of it.
* @return a CallSite whose target can be used to perform capture, generating
* instances of the interface named by {@code invokedType}
* @throws LambdaConversionException If any of the linkage invariants
* described {@link LambdaMetafactory above}
* are violated, or the lookup context
* does not have private access privileges.
*/
public static CallSite metafactory(MethodHandles.Lookup caller,
String invokedName,
MethodType invokedType,
MethodType samMethodType,
MethodHandle implMethod,
MethodType instantiatedMethodType)
throws LambdaConversionException {
AbstractValidatingLambdaMetafactory mf;
mf = new InnerClassLambdaMetafactory(caller, invokedType,
invokedName, samMethodType,
implMethod, instantiatedMethodType,
false, EMPTY_CLASS_ARRAY, EMPTY_MT_ARRAY);
mf.validateMetafactoryArgs();
return mf.buildCallSite();
}
即 lambda 表达式将代码写到私有静态方法中,然后构造目标类型的实现。
为了更直观地看到效果,大家可以在 IDEA 该类运行参数上加上
-Djdk.internal.lambda.dumpProxyClasses=你电脑上的想输出到的路径
可以看到编译出来的内部类:
可以使用 Luyten 反编译工具。
下载地址在这里:
查看源码:
可以看到编译器自动帮我们生成了 Predicate
和 Function
内部类,类名为[目标Class$$Lambda$数字]
的形式,在内部类中调用上面的静态方法。
逻辑层面等价于下面代码:
package other.list;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
public class ListDemo {
public static void main(String[] args) {
List<String> dogNames = new ArrayList<>();
dogNames.add("jam");
dogNames.add("tom cat");
dogNames.add("tom jetty");
dogNames.add("gom jetty");
List<String> tom = dogNames.stream().filter(new ListDemo$$Lambda$1()).map(new ListDemo$$Lambda$2()).collect(Collectors.toList());
System.out.println(tom);
}
private static boolean lambda$min$0(String name) {
return name.startsWith("tom");
}
private static String lambda$main$1(String name) {
return name.toLowerCase();
}
static class ListDemo$$Lambda$1 implements Predicate<String> {
@Override
public boolean test(String name) {
return lambda$min$0(name);
}
}
static class ListDemo$$Lambda$2 implements Function<String, String> {
@Override
public String apply(String name) {
return lambda$main$1(name);
}
}
}
三、拓展
有了上面的讲解,相信大家对 Lambda 表达式已经有了较深的理解。
请大家猜想并动手验证 Map.forEach 写法的底层实现是怎样的?
示例:
import java.util.HashMap;
import java.util.Map;
public class LambdaMapDemo {
public static void main(String[] args) {
Map<Integer, String> map = new HashMap<>();
for (int i = 0; i < 10; i++) {
map.put(i, String.valueOf(i));
}
// 底层如何实现?
map.forEach((k, v) -> {
System.out.println("k:" + k + " -> v:" + v);
});
}
}
相信大家看到下面 main 函数的字节码,应该已经可以脑补出实现方式:
0 new #2 <java/util/HashMap>
3 dup
4 invokespecial #3 <java/util/HashMap.<init> : ()V>
7 astore_1
8 iconst_0
9 istore_2
10 iload_2
11 bipush 10
13 if_icmpge 37 (+24)
16 aload_1
17 iload_2
18 invokestatic #4 <java/lang/Integer.valueOf : (I)Ljava/lang/Integer;>
21 iload_2
22 invokestatic #5 <java/lang/String.valueOf : (I)Ljava/lang/String;>
25 invokeinterface #6 <java/util/Map.put : (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;> count 3
30 pop
31 iinc 2 by 1
34 goto 10 (-24)
37 aload_1
38 invokedynamic #7 <accept, BootstrapMethods #0>
43 invokeinterface #8 <java/util/Map.forEach : (Ljava/util/function/BiConsumer;)V> count 2
48 return
请大家自己写一下逻辑上的等价代码。
希望大家可以有看到 Lambda 表达式,就可以自行脑补出底层实现的能力。
四、总结
很多知识看似习以为常,但是都可以继续深挖,可以学到不一样的知识。
只有真正搞透一个知识点面试中才会有足够的自信。
另外 Lambda 虽好,但不要“贪杯”,滥用 Lambda 对代码的可读性和可维护性都会到来挑战。
创作不易,如果本文对你有帮助,欢迎点赞、收藏加关注,你的支持和鼓励,是我创作的最大动力。