Java Lambda表达式原理解析

前言

java中有lambda,在使用java的过程中我们没少用(啥?用的kotlin?你别说话)但是你知道lambda的实现原理吗?

接下来就来解析lambda实现,不过在lambda之前我们与一个熟悉的老伙计谈谈心————匿名类,为什么因为他们有点类似.

匿名类的实现方式

从字节码的层面上来说new接口和new抽象类是极其抽象且不合理的。

比如这样。

public class Test {
​
    public static void main(String[] args) {
        new Runnable() {
            @Override
            public void run() {
                System.out.println("Hello world");
            }
        }.run();
    }
​
}
​

当我们对这个文件进行编译以后会得到两个文件

Test.class以及Test$1.class

使用jclasslib idea插件对Test.class文件解析以后会发现,我这里new了另外一个东西Test$1,这是个什么?

反编译以后就是这玩意

image-20220731002614499.png 所以new接口和new抽象类这种方式并不会减少类的创建,只不过这个实现类是编译器在编译的时候自动帮助创建。而且这个匿名类的名称是有一定规律的。

小结

匿名类的实现即编译器静态生成一个类,new抽象的实现都是引用的具体的匿名类。

Lambda表达式实现

Lambda和匿名类的实现类似,他们的关系就类似于静态代理和动态代理。

匿名类是静态生成,而Lambda是动态生成。

何以见得?show code

public class Test {
​
    public static void main(String[] args) {
        Runnable run = () -> System.out.println("Hello world")
        run.run();
    }
​
}

这种实现只能通过字节码进行分析。因为他是最直观的方式

这下还没点开就觉得有些猫腻了。hh,这也算是一个伏笔吧。

image-20220731003343103.png 剧透一下

这边这个lambda是我们lambda内部的具体代码.

lambda实现实际是通过asm进行class的生成,然后这个生成的class内部的方法调用了lambda m a i n main 0.

main字节码分析

 0 invokedynamic #2 <run, BootstrapMethods #0>
 5 astore_1
 6 aload_1
 7 invokeinterface #3 <java/lang/Runnable.run : ()V> count 1
12 return

短短几行代码,我却难以读懂...

invokedynamic

invokedynamic是关键。

他调用了常量池2号常量,也就是一个invokeDynamic

image-20220731003913432.png 而这个常量链接了一个描述符和一个Bootstrap方法。

这个方法有些长啊

image-20220731004018422.png 但是这是分析的关键

这是bootstrp方法的具体签名

<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;>

等价于

 public static CallSite metafactory(MethodHandles.Lookup caller,
                                       String invokedName,
                                       MethodType invokedType,
                                       MethodType samMethodType,
                                       MethodHandle implMethod,
                                       MethodType instantiatedMethodType)

也就是说invokedynamic就相当于调用了这个方法

这代码呢也不长

 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();
    }

就3行,第2行是一些参数的配置不太重要,第3行调用的方法是一个抽象。

所以这样来看InnerClassLambdaMetafactory是突破口

小细节

注意其static代码块

static {
    final String dumpProxyClassesKey = "jdk.internal.lambda.dumpProxyClasses";
    // 这里获取了一个System property。看这变量名称能猜出他是一个路径
    String dumpPath = GetPropertyAction.privilegedGetProperty(dumpProxyClassesKey);
    //如果dumpPath为null dumper就是null,否者会生成一个dumper
    dumper = (null == dumpPath) ? null : ProxyClassesDumper.getInstance(dumpPath);
    
    //这里也是做一个配置,不过作为过来人告诉你这个不是很重要。
    final String disableEagerInitializationKey = "jdk.internal.lambda.disableEagerInitialization";
    disableEagerInitialization = AccessController.doPrivileged(
        new GetBooleanAction(disableEagerInitializationKey)).booleanValue();
​

构造函数

public InnerClassLambdaMetafactory(MethodHandles.Lookup caller,
                                   MethodType invokedType,
                                   String samMethodName,
                                   MethodType samMethodType,
                                   MethodHandle implMethod,
                                   MethodType instantiatedMethodType,
                                   boolean isSerializable,
                                   Class<?>[] markerInterfaces,
                                   MethodType[] additionalBridges)
        throws LambdaConversionException {
    //参数配置不重要
    super(caller, invokedType, samMethodName, samMethodType,
          implMethod, instantiatedMethodType,
          isSerializable, markerInterfaces, additionalBridges);
    implMethodClassName = implClass.getName().replace('.', '/');
    implMethodName = implInfo.getName();
    implMethodDesc = implInfo.getMethodType().toMethodDescriptorString();
    constructorType = invokedType.changeReturnType(Void.TYPE);
    lambdaClassName = targetClass.getName().replace('.', '/') + "$$Lambda$" + counter.incrementAndGet();
    //属于是核心逻辑了
    cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
    int parameterCount = invokedType.parameterCount();
    if (parameterCount > 0) {
        argNames = new String[parameterCount];
        argDescs = new String[parameterCount];
        for (int i = 0; i < parameterCount; i++) {
            argNames[i] = "arg$" + (i + 1);
            argDescs[i] = BytecodeDescriptor.unparse(invokedType.parameterType(i));
        }
    } else {
        argNames = argDescs = EMPTY_STRING_ARRAY;
    }
}

接着就是最核心的逻辑了

buildCallSite

CallSite buildCallSite() throws LambdaConversionException {
        final Class<?> innerClass = spinInnerClass();
    ...
    }

第一行代码以后就生成了相应的class文件

spinInnerClass

代码挺长的

private Class<?> spinInnerClass() throws LambdaConversionException {
    String[] interfaces;
    String samIntf = samBase.getName().replace('.', '/');
    boolean accidentallySerializable = !isSerializable && Serializable.class.isAssignableFrom(samBase);
    //参数配置
    ...
​
    //利用asm ClassWriter直接生成class字节码
    cw.visit(CLASSFILE_VERSION, ACC_SUPER + ACC_FINAL + ACC_SYNTHETIC,
             lambdaClassName, null,
             JAVA_LANG_OBJECT, interfaces);
​
    // Generate final fields to be filled in by constructor
    for (int i = 0; i < argDescs.length; i++) {
        FieldVisitor fv = cw.visitField(ACC_PRIVATE + ACC_FINAL,
                                        argNames[i],
                                        argDescs[i],
                                        null, null);
        fv.visitEnd();
    }
​
    generateConstructor();
​
    if (invokedType.parameterCount() != 0 || disableEagerInitialization) {
        generateFactory();
    }
​
    MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, samMethodName,
                                      samMethodType.toMethodDescriptorString(), null, null);
    mv.visitAnnotation("Ljava/lang/invoke/LambdaForm$Hidden;", true);
​
    if (additionalBridges != null) {
        for (MethodType mt : additionalBridges) {
            mv = cw.visitMethod(ACC_PUBLIC|ACC_BRIDGE, samMethodName,
                                mt.toMethodDescriptorString(), null, null);
            mv.visitAnnotation("Ljava/lang/invoke/LambdaForm$Hidden;", true);
            new ForwardingMethodGenerator(mv).generate(mt);
        }
    }
​
​
    ...
​
    cw.visitEnd();
    //转化为字节码
    final byte[] classBytes = cw.toByteArray();
​
    //如果dumper不为空就将class字节码文件输出到指定路径
    if (dumper != null) {
        AccessController.doPrivileged(new PrivilegedAction<>() {
            @Override
            public Void run() {
                dumper.dumpClass(lambdaClassName, classBytes);
                return null;
            }
        }, null,
        new FilePermission("<<ALL FILES>>", "read, write"),
        // createDirectories may need it
        new PropertyPermission("user.dir", "read"));
    }
​
    //定义class
    return UNSAFE.defineAnonymousClass(targetClass, classBytes, null);
}

字节码分析

image-20220731131825139.png

运行以后可以发现生成了一个class文件

image-20220731131852411.png

然而这个class实现了对应的接口,并调用了生成的lambda方法

image-20220731131908453.png 所以lambda表达式的实现原理就简单了,通过asm生成一个类动态地指向我们需要执行的代码块所对应的方法。

猜你喜欢

转载自juejin.im/post/7126485060697456647