Java ASM系列((067)Java 8 Lambda原理探究)

农村四月闲人少,勤学苦攻把名扬。这篇文章主要讲述Java ASM系列:(067)Java 8 Lambda原理探究相关的知识,希望能为你提供帮助。
本文属于Java ASM系列二:OPCODE当中的一篇。
对于《java ASM系列二:OPCODE》有配套的视频讲解,可以点击这里和这里进行查看;同时,也可以点击这里查看源码资料。
在本文当中,我们主要对Java 8 Lambda的两方面进行介绍:

  • 第一方面,如何使用ASM生成Lambda表达式?
  • 第二方面,探究Lambda表达式的实现原理是什么?
1. 使用ASM生成Lambda 1.1. 预期目标
我们的预期目标是生成一个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()方法,其内容如下:
Java ASM系列((067)Java 8 Lambda原理探究)

文章图片

在上图中,我们可以看到mf指向一个InnerClassLambdaMetafactory的实例,并在最后调用了buildCallSite()方法。
接着,我们跳转到java.lang.invoke.InnerClassLambdaMetafactory类的buildCallSite()方法
【Java ASM系列((067)Java 8 Lambda原理探究)】
Java ASM系列((067)Java 8 Lambda原理探究)

文章图片

java.lang.invoke.InnerClassLambdaMetafactory类的spinInnerClass()方法中,找到如下代码:
final byte[] classBytes = cw.toByteArray();

在其语句上,打一个断点:
Java ASM系列((067)Java 8 Lambda原理探究)

文章图片

调试运行,然后使用如下表达式来查看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
    • 当前类带有finalsynthetic标识。
    • 当前类实现了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表达式的内容。
至此,我们可以知道:Lambda表达式的实现,是由JVM调用ASM来实现的。也就是说,JVM使用ASM创建一个匿名内部类(sample/HelloWorld$$Lambda$1),让该匿名内部类实现特定的接口(java/util/function/BiFunction),并在接口定义的方法(apply)实现中包含Lambda表达式的内容。

    推荐阅读