Java Lambda 表达式与方法引用

127 阅读4分钟

Lambda表达式

Lambda表达式是Java 8 引入的一个重要特性,它允许你以更简洁的方式定义匿名内部类的实例,尤其在函数式编程和处理集合数据时非常有用。Lambda表达式提供了一种紧凑、可读性更好的语法,用于传递方法作为参数,从而实现更灵活和简洁的代码。

请注意 Lambda表达式只适用于只有一个方法的接口或者抽象类

Lambda表达式的基本语法如下:

(parameters) -> expression
// 或者
(parameters) -> { statements; }

Lambda表达式的关键部分包括:

  1. 参数列表 (parameters) :Lambda表达式可以有零个、一个或多个参数。参数列表在小括号中定义,如果没有参数,可以直接使用()表示。如果只有一个参数,可以省略小括号。
  2. 箭头符号 (->) :箭头符号用于将参数列表与Lambda表达式的主体分开。
  3. 主体 (expression or statements) :Lambda表达式的主体可以是单个表达式,也可以是一个代码块(使用大括号括起来的语句)。如果主体是单个表达式,那么该表达式的结果会被自动返回。如果主体是一个代码块,你需要显式使用return语句来返回结果。

下面是一些Lambda表达式的示例:

  1. Lambda表达式没有参数:
() -> "Hello, World"
  1. Lambda表达式有一个参数:
  (name) -> "Hello, " + name
  1. Lambda表达式有多个参数:
  (a, b ,c) -> a + b + c
  1. Lambda表达式包含多条语句的代码块:

    (x, y) -> {
        int result = x + y;
        return result;
    }
    

Lambda表达式通常用于函数式接口的实现,这些接口只包含一个抽象方法。例如,java.util.function包中的接口,如 ConsumerPredicateFunction 等,通常用于Lambda表达式的参数类型。Lambda表达式可以传递给方法,或用于操作集合数据,例如在Java 8中引入的Stream API中。

以下是一些示例,展示如何在不同的上下文中使用Lambda表达式:

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");

// 使用Lambda表达式进行集合遍历和打印
names.forEach(name -> System.out.println("Hello, " + name));

// 使用Lambda表达式进行筛选
List<String> longNames = names.stream()
                              .filter(name -> name.length() > 5)
                              .collect(Collectors.toList());

// 使用Lambda表达式自定义排序
Collections.sort(names, (a, b) -> a.compareTo(b));

Lambda表达式使Java更加接近函数式编程的风格,提高了代码的可读性和简洁性,特别是在处理集合、事件处理和并发编程等方面。

方法引用

Java 中的方法引用(Method References)是一种简化Lambda表达式的语法,用于调用已经存在的方法。方法引用可以替代某些简单的Lambda表达式,使代码更加简洁和易读。方法引用允许你将方法作为值传递,而不是编写Lambda表达式。

在方法引用中,你提供了对已经存在的方法的引用,而不是提供一个具体的方法体。方法引用通常用于函数式接口(Functional Interface)的实现,这些接口定义了一个抽象方法,可以通过方法引用来实现。方法引用的语法格式是方法的引用类型::方法名

其中方法的引用类型可以是:

  1. 静态方法引用:引用静态方法。

    ClassName::staticMethodName
    
  2. 实例方法引用:引用特定对象的实例方法。

     object::instanceMethodName
    
  3. 类名引用:引用特定类型的任意对象的实例方法。

    ClassName::instanceMethodName
    
  4. 构造函数引用:引用构造函数。

    ClassName::new
    

使用示例

public class MethodReferencesExamples {

    public static <T> T mergeThings(T a, T b, BiFunction<T, T, T> merger) {
        return merger.apply(a, b);
    }

    public static <T, R> R createString(T a, Function<T, R> merger) {
        return merger.apply(a);
    }

    public static String appendStrings(String a, String b) {
        return a + b;
    }

    public String appendStrings2(String a, String b) {
        return a + b;
    }

    public static void main(String[] args) {

        MethodReferencesExamples myApp = new MethodReferencesExamples();

        // Calling the method mergeThings with a lambda expression
        System.out.println(MethodReferencesExamples.
                mergeThings("Hello ", "World!", (a, b) -> a + b));

        // Reference to a static method
        System.out.println(MethodReferencesExamples.
                mergeThings("Hello ", "World!", MethodReferencesExamples::appendStrings));

        // Reference to an instance method of a particular object        
        System.out.println(MethodReferencesExamples.
                mergeThings("Hello ", "World!", myApp::appendStrings2));

        // Reference to an instance method of an arbitrary object of a
        // particular type
        System.out.println(MethodReferencesExamples.
                mergeThings("Hello ", "World!", String::concat));

        final Function<byte[], String> convert = String::new;
        System.out.println(MethodReferencesExamples.
                createString("Hello World!".getBytes(), convert));
    }
}

输出

Hello World!
Hello World!
Hello World!
Hello World!
Hello World!

原理

使用示例


package online.greatfeng;

public interface RunnableFactory {
    Runnable newRunnable();
}

package online.greatfeng;

public class Main {

    public static void main(String[] args) {
        Main main = new Main();
        main.lambda(main::getRunnable);
    }

    public void lambda(RunnableFactory factory) {
        System.out.println("RunnableFactory.class : " + factory.getClass());
        final Runnable runnable = factory.newRunnable();
        System.out.println("Runnable.class : " + runnable.getClass());
        runnable.run();
    }

    public Runnable getRunnable() {
        return () -> System.out.println("Hello Lambda!");
    }
}

添加虚拟机参数并运行

-Djdk.internal.lambda.dumpProxyClasses=.
-Djdk.invoke.LambdaMetafactory.dumpProxyClassFiles=true

会动态生成两个class文件

Main$$Lambda.0x00000007c00c8c28.class image.png

newRunnable()会调用main.getRunnable()方法返回一个 Runnable 对象

Main$$Lambda.0x00000007c00c9c00.class image.png

这个 Runnable 的 run 方法会调用 编译器生成的 Main 的静态方法 lambda$getRunnable$0()

运行输入

RunnableFactory.class : class online.greatfeng.Main$$Lambda/0x00000007c00c8c28
Runnable.class : class online.greatfeng.Main$$Lambda/0x00000007c00c9c00
Hello Lambda!

可通过本人写的这个类库打印 Main.class 字节码 ClassParser

可看出 Main.java 通过javac编译器 生成 Main.class 文件时添加了一个辅助方法 202312012031687.png

标记1

  • access_flags : ACC_SYNTHETIC 标记代表是编译器生成,不是源代码文件中有的,方法名为
  • name_index :lambda$getRunnable$0 为方法名

标记2: CodeAttribute 属性中只打印了一个Hello Lambda! 就是 getRunnable 的方法体

invokedynamic 指令

invokedynamic 是 Java 虚拟机(JVM)指令的一种,引入于 Java 7,为了提高 JVM 在处理动态语言和函数式编程时的灵活性和性能。主要用于支持动态类型语言、Lambda 表达式和其他需要在运行时进行方法绑定的情况。

invokedynamic 官方链接

下面是关于 invokedynamic 字节码指令的一些基本介绍:

  1. 指令格式: invokedynamic 字节码指令的格式如下:
invokedynamic CONSTANT_InvokeDynamic_info 常量池的索引
  1. CONSTANT_InvokeDynamic_info: 包含
  • 一个 BootstrapMethod
  • 方法签名

main() 方法的字节码

#1  MethodInfo{
   access_flags 9 ACC_PUBLIC ACC_STATIC 
   name_index #71 =  CpInfoUtf8(data = main)
   descriptor_index #72 =  CpInfoUtf8(data = ([Ljava/lang/String;)V)   attributes_count 1   attributes = attributes[0] = 
     CodeAttribute{
         max_stack = 3
         max_locals = 2
         codes = 
          code[0] = 187 new args[#0] = CpInfoClass(data = online/greatfeng/Main)
          code[3] = 89 dup
          code[4] = 183 invokespecial args[#0] = CpInfoMethodref(classIndex = #7 nameAndTypeIndex = #3 data = online/greatfeng/Main . <init> # ()V)
          code[7] = 76 astore_1
          code[8] = 43 aload_1
          code[9] = 43 aload_1
          code[10] = 89 dup
          code[11] = 184 invokestatic args[#0] = CpInfoMethodref(classIndex = #11 nameAndTypeIndex = #12 data = java/util/Objects . requireNonNull # (Ljava/lang/Object;)Ljava/lang/Object;)
          code[14] = 87 pop
          code[15] = 186 invokedynamic args[#0] = CpInfoInvokeDynamic(bootstrapMethodAttrIndex = #0 ,nameAndTypeIndex = #17 ,data = CpInfoNameAndType(nameIndex = #18, descriptorIndex = #19, data = newRunnable # (Lonline/greatfeng/Main;)Lonline/greatfeng/RunnableFactory; )) args[#1] = null
          code[20] = 182 invokevirtual args[#0] = CpInfoMethodref(classIndex = #7 nameAndTypeIndex = #21 data = online/greatfeng/Main . lambda # (Lonline/greatfeng/RunnableFactory;)V)
          code[23] = 177 Return
     
 }

code[15] 有一条指令 invokedynamic

  • bootstrapMethodAttrIndex = #0

查看引导方法表 BootstrapMethods ,为 LambdaMetafactory.metafactory() 静态方法生成一个调用点 CallSite

  • 方法签名,方法名为 newRunnable 参数为 Main; ,返回值为 RunnableFactory; 相当于 new Main$$Lambda.0x00000007c00c8c28.class(this)

getRunnable()方法的字节码

#3  MethodInfo{
   access_flags 1 ACC_PUBLIC 
   name_index #79 =  CpInfoUtf8(data = getRunnable)
   descriptor_index #54 =  CpInfoUtf8(data = ()Ljava/lang/Runnable;)
   attributes_count 1
   attributes = attributes[0] = 
     CodeAttribute{
         max_stack = 1
         max_locals = 1
         codes = 
          code[0] = 186 invokedynamic args[#0] = CpInfoInvokeDynamic(bootstrapMethodAttrIndex = #3 ,nameAndTypeIndex = #63 ,data = CpInfoNameAndType(nameIndex = #61, descriptorIndex = #54, data = run # ()Ljava/lang/Runnable; )) args[#1] = null
          code[5] = 176 areturn
        }
     
 }

code[0] 有一条指令 invokedynamic

  • bootstrapMethodAttrIndex = #3

查看引导方法表 BootstrapMethods ,为 LambdaMetafactory.metafactory() 静态方法生成一个调用点 CallSite

  • 方法签名,方法名为 run 参数为为空() ,返回值为 Runnable;相当于 new Main$$Lambda.0x00000007c00c9c00.class()

LambdaMetafactory

public final class LambdaMetafactory {
    public static CallSite metafactory(MethodHandles.Lookup caller,
                                       String interfaceMethodName,
                                       MethodType factoryType,
                                       MethodType interfaceMethodType,
                                       MethodHandle implementation,
                                       MethodType dynamicMethodType)
            throws LambdaConversionException {
        AbstractValidatingLambdaMetafactory mf;
        mf = new InnerClassLambdaMetafactory(Objects.requireNonNull(caller),
                                             Objects.requireNonNull(factoryType),
                                             Objects.requireNonNull(interfaceMethodName),
                                             Objects.requireNonNull(interfaceMethodType),
                                             Objects.requireNonNull(implementation),
                                             Objects.requireNonNull(dynamicMethodType),
                                             false,
                                             EMPTY_CLASS_ARRAY,
                                             EMPTY_MT_ARRAY);
        mf.validateMetafactoryArgs();
        return mf.buildCallSite();
    }
}

引导方法表 BootstrapMethods

BootstrapMethod[0] = 
      BootstrapMethod(bootstrap_method_ref=#96 = CpInfoMethodHandle(referenceKind = REF_invokeStatic ,referenceIndex = #97 data = REF_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;) 
       bootstrap_arguments=
              bootstrap_argument[0] = #84 +CpInfoMethodType(descriptorIndex = #54 ,data = ()Ljava/lang/Runnable;) 
              bootstrap_argument[1] = #85 +CpInfoMethodHandle(referenceKind = REF_invokeVirtual ,referenceIndex = #86 data = REF_invokeVirtual . online/greatfeng/Main . getRunnable # ()Ljava/lang/Runnable;) 
              bootstrap_argument[2] = #84 +CpInfoMethodType(descriptorIndex = #54 ,data = ()Ljava/lang/Runnable;) 

BootstrapMethod[1] = 
      BootstrapMethod(bootstrap_method_ref=#103 = CpInfoMethodHandle(referenceKind = REF_invokeStatic ,referenceIndex = #104 data = REF_invokeStatic . java/lang/invoke/StringConcatFactory . makeConcatWithConstants # (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;) 
       bootstrap_arguments=
              ootstrap_argument[0] = #88 +CpInfoString(index = #89 data = RunnableFactory.class : ) 

BootstrapMethod[2] = 
      BootstrapMethod(bootstrap_method_ref=#103 = CpInfoMethodHandle(referenceKind = REF_invokeStatic ,referenceIndex = #104 data = REF_invokeStatic . java/lang/invoke/StringConcatFactory . makeConcatWithConstants # (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;) 
       bootstrap_arguments=
              bootstrap_argument[0] = #90 +CpInfoString(index = #91 data = Runnable.class : ) 

BootstrapMethod[3] = 
      BootstrapMethod(bootstrap_method_ref=#96 = CpInfoMethodHandle(referenceKind = REF_invokeStatic ,referenceIndex = #97 data = REF_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;) 
       bootstrap_arguments=
              bootstrap_argument[0] = #92 +CpInfoMethodType(descriptorIndex = #6 ,data = ()V) 
              bootstrap_argument[1] = #93 +CpInfoMethodHandle(referenceKind = REF_invokeStatic ,referenceIndex = #94 data = REF_invokeStatic . online/greatfeng/Main . lambda$getRunnable$0 # ()V) 
              bootstrap_argument[2] = #92 +CpInfoMethodType(descriptorIndex = #6 ,data = ()V) 

最终这段代码可以翻译为

package online.greatfeng;

public class Main {

    public static void main(String[] args) {
        Main main = new Main();
        main.lambda(new online.greatfeng.Main$$Lambda/0x00000007c00c8c28(this));
    }

    public void lambda(RunnableFactory factory) {
        System.out.println("RunnableFactory.class : " + factory.getClass());
        final Runnable runnable = factory.newRunnable();
        System.out.println("Runnable.class : " + runnable.getClass());
        runnable.run();
    }
    public Runnable getRunnable() {
        return online.greatfeng.Main$$Lambda/0x00000007c00c9c00();
    }

    private static void lambda$getRunnable$0(){
        System.out.println("Hello Lambda!");
    }
}