Deep understanding of Lambda expressions

Posted by said_r3000 on Tue, 15 Feb 2022 01:49:20 +0100

1, Background

Lambda expressions in Java 8 are no longer a "new feature". Many people now use Lambda expressions in their work. But do you really understand the underlying principles of Lambda expressions?

This paper gives its own understanding, hoping to be helpful to you.

2, Analysis

The following is a very simple code, which uses 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);
    }
}

We use the Jclasslib plug-in( "Those IDEA plug-ins who hate to meet late" See bytecode:

You can also use javac and javap instructions to execute on the command line. For example:

 javac ListDemo.java
 javap -p -s -c -v -l ListDemo

We can see one more inner class and bootstrap methods

lambdamain0

0 aload_0
1 ldc #20 <tom>
3 invokevirtual #21 <java/lang/String.startsWith : (Ljava/lang/String;)Z>
6 ireturn

amount to:

private static boolean lambda$main$0(String name){
   return name.startsWith("tom");
}

lambdamain1

0 aload_0
1 invokevirtual #19 <java/lang/String.toLowerCase : ()Ljava/lang/String;>
4 areturn

amount to

private static String lambda$main$1(String name){
   return name.toLowerCase();
}

From the above simple analysis, we can see that in essence, lambda expressions will eventually be compiled into private static methods.

main method

  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

Dynamic method calls are performed through the invokedynamic instruction.

as

50 invokedynamic #10 <test, BootstrapMethods #0>

We can directly click the command on the plug-in to jump to the corresponding official description document: https://docs.oracle.com/javase/specs/jvms/se16/html/jvms-6.html#jvms-6.5.invokedynamic

It can be seen from the JVM document that this method is to realize dynamic call calculation. It is transformed into dynamic calculation call through constant pool key amount symbol reference, in which CallSite instance is the target method call instance.

You can keep up in the plug-in

The #10 in the constant pool in invokedynamic #10 is the following:

Where BootstrapMethods # 0 corresponds to

You can see that this is for Java lang.invoke. Lambdametafactory #metafactory calls, and the return value is Java lang.invoke. Callsite object, which represents the actual target method call.

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

That is, the lambda expression writes the code into the private static method, and then constructs the implementation of the target type.

In order to see the effect more intuitively, you can add

-Djdk.internal.lambda.dumpProxyClasses = the path you want to output to on your computer

You can see the compiled internal classes:

have access to Luyten Decompile tool. Download address here:

View source code:

It can be seen that the compiler automatically generated the Predicate and Function inner classes, called the "target Class$Lambda digits", calling the static method in the internal class.

The logical level is equivalent to the following code:

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

3, Expand

With the above explanation, I believe you have a deep understanding of Lambda expressions.

Please guess and verify the map What is the underlying implementation of foreach?

Example:

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

        // How to implement the bottom layer?
        map.forEach((k, v) -> {
            System.out.println("k:" + k + " -> v:" + v);
        });
    }
}

I believe you can see the bytecode of the main function below, and you should be able to figure out the implementation method:

 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

Please write your own logical equivalent code.

I hope you can see the Lambda expression and make up the ability of the underlying implementation by yourself.

4, Summary

A lot of knowledge seems to be used to it, but we can continue to dig deep and learn different knowledge. Only when we really understand a knowledge point can we have enough confidence in the interview.

In addition, although Lambda is good, don't be greedy. Abusing Lambda will challenge the readability and maintainability of the code.

Please indicate the source of Reprint: https://blog.csdn.net/w605283073/article/details/121571573