深入理解 Lambda 表达式

2021-11-29 11:17:16 浏览数 (1)

一、背景

Java 8 的 Lambda 表达式已经不再是“新特性”。 现在很多人工作中会使用 Lambda 表达式。 但是,你是否真正理解 Lambda 表达式的底层原理?

本文给出自己的理解,希望对大家有帮助。

二、分析

下面是一段非常简单的代码,其中用到了 Stream

代码语言:javascript复制
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插件》 中有介绍),查看字节码:

我们可以看到多出一个内部类和 BootstrapMethods

lambdamain0

代码语言:javascript复制
0 aload_0
1 invokevirtual #56 <other/list/DogDO.getName : ()Ljava/lang/String;>
4 ldc #67 <tom>
6 invokevirtual #69 <java/lang/String.startsWith : (Ljava/lang/String;)Z>
9 ireturn

相当于:

代码语言:javascript复制
private static Boolean lambda$main$0(DogDO dogDo){
   return dogDo.getName().startsWith("tom");
}

lambdamain1

代码语言:javascript复制
0 aload_0
1 invokevirtual #56 <other/list/DogDO.getName : ()Ljava/lang/String;>
4 invokevirtual #62 <java/lang/String.toLowerCase : ()Ljava/lang/String;>
7 areturn

相当于

代码语言:javascript复制
private static String lambda$main$1(DogDO dogDo){
   return dogDo.getName().toLowerCase();
}

通过上述简单分析就可以看出来,本质上 lambda 表达式最终会被编译为私有静态方法。

main 方法

代码语言:javascript复制
 0 new #7 <java/util/ArrayList>
 3 dup
 4 invokespecial #9 <java/util/ArrayList.<init> : ()V>
 7 astore_1
 8 aload_1
 9 invokeinterface #10 <java/util/List.stream : ()Ljava/util/stream/Stream;> count 1
14 invokedynamic #16 <test, BootstrapMethods #0>
19 invokeinterface #20 <java/util/stream/Stream.filter : (Ljava/util/function/Predicate;)Ljava/util/stream/Stream;> count 2
24 invokedynamic #26 <apply, BootstrapMethods #1>
29 invokeinterface #30 <java/util/stream/Stream.map : (Ljava/util/function/Function;)Ljava/util/stream/Stream;> count 2
34 invokestatic #34 <java/util/stream/Collectors.toList : ()Ljava/util/stream/Collector;>
37 invokeinterface #40 <java/util/stream/Stream.collect : (Ljava/util/stream/Collector;)Ljava/lang/Object;> count 2
42 checkcast #11 <java/util/List>
45 astore_2
46 getstatic #44 <java/lang/System.out : Ljava/io/PrintStream;>
49 aload_2
50 invokevirtual #50 <java/io/PrintStream.println : (Ljava/lang/Object;)V>
53 return

通过 invokedynamic 指令执行动态方法调用。

代码语言:javascript复制
14 invokedynamic #16 <test, BootstrapMethods #0>

可以在插件里一直跟下去

其中 BootstrapMethods # 0 对应

可以看到这是对 java.lang.invoke.LambdaMetafactory#metafactory 的调用,返回值是 java.lang.invoke.CallSite 对象,这个对象代表了真正执行的目标方法调用。

代码语言:javascript复制
 /**
     * 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 bootstrap method for {@code invokedynamic}
     * call sites, to support the lambda expression and method
     * reference expression features of the Java Programming Language.
     *
     * 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}.
     *
     * 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
     *               private access
     *               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 表达式将代码写到私有静态方法中,然后构造目标类型的实现。

逻辑层面等价于下面代码:

代码语言:javascript复制
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<DogDO> dogs = new ArrayList<>();

        List<String> tom = dogs.stream().filter(new Predicate<DogDO>() {
            @Override
            public boolean test(DogDO dogDO) {
                return lambda$main$0(dogDO);
            }
        }).map(new Function<DogDO, String>() {

            @Override
            public String apply(DogDO dogDO) {
                return lambda$main$1(dogDO);
            }
        }).collect(Collectors.toList());
        System.out.println(tom);
    }

    private static Boolean lambda$min$0(DogDO dogDo) {
        return dogDo.getName().startsWith("tom");
    }

    private static String lambda$main$1(DogDO dogDo) {
        return dogDo.getName().toLowerCase();
    }

}

本文只是抛砖引玉,大家还可以继续深入研究。

三、总结

很多知识看似习以为常,但是都可以继续深挖,可以学到不一样的知识。

创作不易,如果本文对你有帮助,你的支持和鼓励,是我创作的最大动力。

0 人点赞