农村四月闲人少,勤学苦攻把名扬。这篇文章主要讲述Java ASM系列:(067)Java 8 Lambda原理探究相关的知识,希望能为你提供帮助。
本文属于Java ASM系列二:OPCODE当中的一篇。
对于《java ASM系列二:OPCODE》有配套的视频讲解,可以点击这里和这里进行查看;同时,也可以点击这里查看源码资料。
在本文当中,我们主要对Java 8 Lambda的两方面进行介绍:
- 第一方面,如何使用ASM生成Lambda表达式?
- 第二方面,探究Lambda表达式的实现原理是什么?
我们的预期目标是生成一个
HelloWorld
类,代码如下:import java.util.function.BiFunction;
public class HelloWorld {
public void test() {
BiFunction<
Integer, Integer, Integer>
func = Math::max;
Integer result = func.apply(10, 20);
System.out.println(result);
}
}
1.2. 编码实现
在下面的代码中,我们重点关注两个点:
- 第一点,是
Handle
实例的创建。 - 第二点,是
MethodVisitor.visitInvokeDynamicInsn()
方法。
import lsieun.utils.FileUtils;
import org.objectweb.asm.*;
import static org.objectweb.asm.Opcodes.*;
public class HelloWorldGenerateCore {
public static void main(String[] args) throws Exception {
String relative_path = "sample/HelloWorld.class";
String filepath = FileUtils.getFilePath(relative_path);
// (1) 生成byte[]内容
byte[] bytes = dump();
// (2) 保存byte[]到文件
FileUtils.writeBytes(filepath, bytes);
}public static byte[] dump() throws Exception {
// (1) 创建ClassWriter对象
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
// (2) 调用visitXxx()方法
cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, "sample/HelloWorld",
null, "java/lang/Object", null);
{
MethodVisitor mv1 = cw.visitMethod(ACC_PUBLIC, "<
init>
", "()V", null, null);
mv1.visitCode();
mv1.visitVarInsn(ALOAD, 0);
mv1.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<
init>
", "()V", false);
mv1.visitInsn(RETURN);
mv1.visitMaxs(0, 0);
mv1.visitEnd();
}{
MethodVisitor mv2 = cw.visitMethod(ACC_PUBLIC, "test", "()V", null, null);
mv2.visitCode();
// 第1点,Handle实例的创建
Handle bootstrapMethodHandle = new Handle(H_INVOKESTATIC, "java/lang/invoke/LambdaMetafactory", "metafactory",
"(Ljava/lang/invoke/MethodHandles$Lookup;
Ljava/lang/String;
Ljava/lang/invoke/MethodType;
Ljava/lang/invoke/MethodType;
" +
"Ljava/lang/invoke/MethodHandle;
Ljava/lang/invoke/MethodType;
)Ljava/lang/invoke/CallSite;
", false);
// 第2点,MethodVisitor.visitInvokeDynamicInsn()方法的调用
mv2.visitInvokeDynamicInsn("apply", "()Ljava/util/function/BiFunction;
",
bootstrapMethodHandle,
Type.getType("(Ljava/lang/Object;
Ljava/lang/Object;
)Ljava/lang/Object;
"),
new Handle(H_INVOKESTATIC, "java/lang/Math", "max", "(II)I", false),
Type.getType("(Ljava/lang/Integer;
Ljava/lang/Integer;
)Ljava/lang/Integer;
")
);
mv2.visitVarInsn(ASTORE, 1);
mv2.visitVarInsn(ALOAD, 1);
mv2.visitIntInsn(BIPUSH, 10);
mv2.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;
", false);
mv2.visitIntInsn(BIPUSH, 20);
mv2.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;
", false);
mv2.visitMethodInsn(INVOKEINTERFACE, "java/util/function/BiFunction", "apply",
"(Ljava/lang/Object;
Ljava/lang/Object;
)Ljava/lang/Object;
", true);
mv2.visitTypeInsn(CHECKCAST, "java/lang/Integer");
mv2.visitVarInsn(ASTORE, 2);
mv2.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;
");
mv2.visitVarInsn(ALOAD, 2);
mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/Object;
)V", false);
mv2.visitInsn(RETURN);
mv2.visitMaxs(0, 0);
mv2.visitEnd();
}cw.visitEnd();
// (3) 调用toByteArray()方法
return cw.toByteArray();
}
}
1.3. 验证结果
接下来,我们来验证生成的Lambda表达式是否能够正常运行。
import java.lang.reflect.Method;
public class HelloWorldRun {
public static void main(String[] args) throws Exception {
Class<
?>
clazz = Class.forName("sample.HelloWorld");
Object obj = clazz.newInstance();
Method m = clazz.getDeclaredMethod("test");
m.invoke(obj);
}
}
2. 探究Lambda的实现原理简单来说,Lambda表达式的内部原理,是借助于Java ASM生成匿名内部类来实现的。
2.1. 追踪Lambda
首先,我们使用
javap -v -p sample.HelloWorld
命令查看输出结果:$ javap -v -p sample.HelloWorld
...
BootstrapMethods:
0: #28 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(
Ljava/lang/invoke/MethodHandles$Lookup;
Ljava/lang/String;
Ljava/lang/invoke/MethodType;
Ljava/lang/invoke/MethodType;
Ljava/lang/invoke/MethodHandle;
Ljava/lang/invok e/MethodType;
)Ljava/lang/invoke/CallSite;
Method arguments:
#29 (Ljava/lang/Object;
Ljava/lang/Object;
)Ljava/lang/Object;
#30 invokestatic java/lang/Math.max:(II)I
#31 (Ljava/lang/Integer;
Ljava/lang/Integer;
)Ljava/lang/Integer;
通过上面的输出结果,我们定位到BootstrapMethods的部分,可以看到它使用了
java.lang.invoke.LambdaMetafactory
类的metafactory()
方法。至此,我们想表达的意思:Lambda表达式是与
LambdaMetafactory.metafactory()
方法有关联关系的。在IDE当中,我们可以查看
LambdaMetafactory.metafactory()
方法,其内容如下:文章图片
在上图中,我们可以看到
mf
指向一个InnerClassLambdaMetafactory
的实例,并在最后调用了buildCallSite()
方法。接着,我们跳转到
java.lang.invoke.InnerClassLambdaMetafactory
类的buildCallSite()
方法【Java ASM系列((067)Java 8 Lambda原理探究)】
文章图片
在
java.lang.invoke.InnerClassLambdaMetafactory
类的spinInnerClass()
方法中,找到如下代码:final byte[] classBytes = cw.toByteArray();
在其语句上,打一个断点:
文章图片
调试运行,然后使用如下表达式来查看
classBytes
的值:Arrays.toString(classBytes)
其实,这里的
classBytes
就是生成的类文件的字节码内容,这样的字符串内容就是该字节码内容的另一种表现形式。那么,拿到这样一个字符串内容之后,我们应该如何处理呢?2.2. 查看生成的类
在项目代码中,有一个
LambdaRun
类,它的作用就是将上述字符串内容的类信息打印出来。import lsieun.utils.StringUtils;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.util.Printer;
import org.objectweb.asm.util.Textifier;
import org.objectweb.asm.util.TraceClassVisitor;
import java.io.IOException;
import java.io.PrintWriter;
public class LambdaRun {
public static void main(String[] args) throws IOException {
// (1) 设置参数
String str = "[-54, -2, -70, -66, ...]";
byte[] bytes = StringUtils.array2Bytes(str);
int parsingOptions = ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG;
// (2) 打印结果
Printer printer = new Textifier();
PrintWriter printWriter = new PrintWriter(System.out, true);
TraceClassVisitor traceClassVisitor = new TraceClassVisitor(null, printer, printWriter);
new ClassReader(bytes).accept(traceClassVisitor, parsingOptions);
}
}
我们可以将代表字节码内容的字符串放到上面代码的
str
变量中,然后运行LambdaRun
可以得到如下结果:// class version 52.0 (52)
// access flags 0x1030
final synthetic class sample/HelloWorld$$Lambda$1 implements java/util/function/BiFunction {// access flags 0x2
private <
init>
()V
ALOAD 0
INVOKESPECIAL java/lang/Object.<
init>
()V
RETURN
MAXSTACK = 1
MAXLOCALS = 1// access flags 0x1
public apply(Ljava/lang/Object;
Ljava/lang/Object;
)Ljava/lang/Object;
@Ljava/lang/invoke/LambdaForm$Hidden;
()
ALOAD 1
CHECKCAST java/lang/Integer
INVOKEVIRTUAL java/lang/Integer.intValue ()I
ALOAD 2
CHECKCAST java/lang/Integer
INVOKEVIRTUAL java/lang/Integer.intValue ()I
INVOKESTATIC java/lang/Math.max (II)I
INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
ARETURN
MAXSTACK = 2
MAXLOCALS = 3
}
通过上面的输出结果,我们可以看到:
- 第一点,当前类的名字叫
sample/HelloWorld$$Lambda$1
。- 当前类带有
final
和synthetic
标识。 - 当前类实现了
java/util/function/BiFunction
接口。
- 当前类带有
- 第二点,在
sample/HelloWorld$$Lambda$1
类当中,它定义了一个构造方法(& lt; init& gt; ()V
)。 - 第三点,在
sample/HelloWorld$$Lambda$1
类当中,它定义了一个apply
方法(apply(Ljava/lang/Object; Ljava/lang/Object; )Ljava/lang/Object;
)。- 这个
apply
方法正是BiFunction
接口中定义的方法。 apply
方法的内部代码逻辑,是通过调用Math.max()
方法来实现的;而Math::max
正是Lambda表达式的内容。
- 这个
sample/HelloWorld$$Lambda$1
),让该匿名内部类实现特定的接口(java/util/function/BiFunction
),并在接口定义的方法(apply
)实现中包含Lambda表达式的内容。推荐阅读
- 国产接口工具ApiPost如何利用CryptoJS对请求参数进行MD5/AES加解密
- 在linux中添加字体
- 自主创新国产化科技(智能制造之 SMT 产线监控管理可视化)
- Alibaba微服务技术系列「Dubbo3.0技术专题」回顾Dubbo2.x的技术原理和功能实现
- 一起玩转树莓派(11)——使用LCD屏
- 关于静态代码块的执行顺序,很简单的一道题,应该所有人都会吧()
- vivo全球商城时光机 - 大型促销活动保障利器
- redis笔记01_李孟_新浪博客
- 如何理解虚拟机类加载过程详解