Lambda原理及应用

Lambda原理及应用

Lambda介绍

Lambda 是 JDK8 以后版本推出的一个新特性,也是一个重要的版本更新,利用 Lambda 可以简化内部类,可以更方便的进行集合的运算,让你的代码看起来更加简洁,也能提升代码的运行效率

Lambda语法

非静调用

(parameters...) -> expression

或者

(parameters...) ->{ statements; }

静态调用

(parameters...) -> Class.Method(parameters...)

通常也可以简写成

Class::Method

举个例子

() -> 1 
i -> 2 * i  
(String s) -> {System.out.print(s);}
(o1, o2) -> Integer.compare(o1, o2)
Integer::compare

实现 Lambda 的基础

MethodHandle

MethodHandle 和 Reflection 都可以分派方法调用,但是 MethodHandle 比 Reflection 更强大,它是模拟字节码层次的方法分派。

MethodHandle 是结合 invokedynamic 指令一起为动态语言服务的,也就是说 MethodHandle (更准确的来说是其设计理念)是服务于所有运行在JVM之上的语言,而 Relection 则只是适用 Java 语言本身。

例子

通过MethodHandle 实现方法调用,调用 String#substring。

package org.lin;


import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;

public class MethodHandleTest {

    public MethodHandle getHandler() throws NoSuchMethodException, IllegalAccessException {

        //方法签名
        MethodType mt = MethodType.methodType(String.class, int.class, int.class);
        //上下文
        MethodHandles.Lookup lk = MethodHandles.lookup();
        //
        MethodHandle mh = lk.findVirtual(String.class, "substring", mt);
        return mh;
    }

    public static void main(String[] args) throws Throwable {
        MethodHandle mh = new MethodHandleTest().getHandler();
        String str = "hello world";
        Object result1 = mh.invoke(str, 1, 3);
//        Object result2 = (String) mh.invokeExact(str, 1, 3);
        System.out.println("MethodHandle invoke: " + result1);
        System.out.println("substring: " + str.substring(1,3));
    }
}

运行结果:

MethodHandle invoke: el
substring: el

查看字节码:

// class version 60.0 (60)
// access flags 0x21
public class org/lin/MethodHandleTest {

  // compiled from: MethodHandleTest.java
  // access flags 0x19
  public final static INNERCLASS java/lang/invoke/MethodHandles$Lookup java/lang/invoke/MethodHandles Lookup

  // access flags 0x1
  public <init>()V
   L0
    LINENUMBER 8 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
    RETURN
   L1
    LOCALVARIABLE this Lorg/lin/MethodHandleTest; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x1
  public getHandler()Ljava/lang/invoke/MethodHandle; throws java/lang/NoSuchMethodException java/lang/IllegalAccessException 
   L0
    LINENUMBER 13 L0
    LDC Ljava/lang/String;.class
    GETSTATIC java/lang/Integer.TYPE : Ljava/lang/Class;
    ICONST_1
    ANEWARRAY java/lang/Class
    DUP
    ICONST_0
    GETSTATIC java/lang/Integer.TYPE : Ljava/lang/Class;
    AASTORE
    INVOKESTATIC java/lang/invoke/MethodType.methodType (Ljava/lang/Class;Ljava/lang/Class;[Ljava/lang/Class;)Ljava/lang/invoke/MethodType;
    ASTORE 1
   L1
    LINENUMBER 15 L1
    INVOKESTATIC java/lang/invoke/MethodHandles.lookup ()Ljava/lang/invoke/MethodHandles$Lookup;
    ASTORE 2
   L2
    LINENUMBER 17 L2
    ALOAD 2
    LDC Ljava/lang/String;.class
    LDC "substring"
    ALOAD 1
    INVOKEVIRTUAL java/lang/invoke/MethodHandles$Lookup.findVirtual (Ljava/lang/Class;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/MethodHandle;
    ASTORE 3
   L3
    LINENUMBER 18 L3
    ALOAD 3
    ARETURN
   L4
    LOCALVARIABLE this Lorg/lin/MethodHandleTest; L0 L4 0
    LOCALVARIABLE mt Ljava/lang/invoke/MethodType; L1 L4 1
    LOCALVARIABLE lk Ljava/lang/invoke/MethodHandles$Lookup; L2 L4 2
    LOCALVARIABLE mh Ljava/lang/invoke/MethodHandle; L3 L4 3
    MAXSTACK = 6
    MAXLOCALS = 4

  // access flags 0x9
  public static main([Ljava/lang/String;)V throws java/lang/Throwable 
   L0
    LINENUMBER 22 L0
    NEW org/lin/MethodHandleTest
    DUP
    INVOKESPECIAL org/lin/MethodHandleTest.<init> ()V
    INVOKEVIRTUAL org/lin/MethodHandleTest.getHandler ()Ljava/lang/invoke/MethodHandle;
    ASTORE 1
   L1
    LINENUMBER 23 L1
    LDC "hello world"
    ASTORE 2
   L2
    LINENUMBER 24 L2
    ALOAD 1
    ALOAD 2
    ICONST_1
    ICONST_3
    INVOKEVIRTUAL java/lang/invoke/MethodHandle.invoke (Ljava/lang/String;II)Ljava/lang/Object;
    ASTORE 3
   L3
    LINENUMBER 26 L3
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    ALOAD 3
    INVOKEDYNAMIC makeConcatWithConstants(Ljava/lang/Object;)Ljava/lang/String; [
      // handle kind 0x6 : 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;
      // arguments:
      "MethodHandle invoke: \u0001"
    ]
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L4
    LINENUMBER 27 L4
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    ALOAD 2
    ICONST_1
    ICONST_3
    INVOKEVIRTUAL java/lang/String.substring (II)Ljava/lang/String;
    INVOKEDYNAMIC makeConcatWithConstants(Ljava/lang/String;)Ljava/lang/String; [
      // handle kind 0x6 : 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;
      // arguments:
      "substring: \u0001"
    ]
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L5
    LINENUMBER 28 L5
    RETURN
   L6
    LOCALVARIABLE args [Ljava/lang/String; L0 L6 0
    LOCALVARIABLE mh Ljava/lang/invoke/MethodHandle; L1 L6 1
    LOCALVARIABLE str Ljava/lang/String; L2 L6 2
    LOCALVARIABLE result1 Ljava/lang/Object; L3 L6 3
    MAXSTACK = 4
    MAXLOCALS = 4
}

从字节码上也看到 MethodHandle 是通过 invokedynamic 指令实现的,基于指令实现,所以说是在 JVM 层级实现的。

Invokedynamic

Invokedynamic 是 JDK7 引入的一条新指令,通过一个调用点 CallSite 和 方法句柄 MethodHandle,来完成一个方法的调用。
CallSite 就是一个 MethodHandle 的 Holder,MethodHandle 指向一个调用点真正执行的方法。

Invokedynamic 是 JDK7 为了支持动态类型语言而新增的字节码指令,简单的理解,invokedynamic 在字节码层面实现了 MethodHandle 实现的功能。

对比之前 Java 字节码所有的方法分派均依赖于单纯的符号引用,即在编译生成的.class字节码文件中,4条方法调用指(invokevirtual、invokespecial、invokestatic、invokeinterface)后面跟的操作数均为明确的CONSTANT_Methodref_info 或 CONSTANT_InterfaceMethodref_info 常量池符号引用,这就意味着在编译期就提前把方法调用绑定到了某类型及其子类型。

例如:

System.out.println("substring: " + str.substring(1,3));

从字节码,可以看到 invokevirtual 编译期就绑定了对应的类和方法。

   INVOKEVIRTUAL java/lang/String.substring (II)Ljava/lang/String;
   INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V

Invokedynamic 最大的区别的它是一个动态绑定,在编译期没有进行类和方法的绑定,而是运行的时候动态解析和绑定。

LambdaMetafactory

LambdaMetafactory 是 JDK 内部 CallSite 和 MethodHandle 绑定实现类。

LambdaMetafactory的关键方法

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();
    }
  • MethodHandles.Lookup caller – 调用者,也就是 Invokedynamic 指令运行的上下文,通常可以通过 Lookup#lookupClass() 获取。
  • String invokedName – Lambda 实现的接口的方法名称。
  • MethodType invokedType – 调用点的方法签名描述 ,
  • MethodType samMethodType – Lambda 实现的接口方法的签名描述 。(sam 就 single public abstract method 的缩写)
  • MethodHandle implMethod – 生成的脱糖方法 desugaring Method签名。
  • MethodType instantiatedMethodType – 对应 samMethodType 的运行时的方法签名,在非泛型的情况下于 samMethodType 相同。

VM Anonymous Class

VM Anonymous Class 内存级别匿名类,不需要 ClassLoader 加载,没有类名,当然也没其他权限管理等操作,这意味着效率更高(不必要的锁操作)、GC 更方便(没有 ClassLoader);
VM Anonymous Class 通过调用 sun.misc.Unsafe.defineAnonymousClass 生成。

LambdaMetafactory#metafactory 对应的实现 InnerClassLambdaMetafactory 会生成一个 VM Anonymous Class 实现方法调用。

Lambda的原理

例子

我们来看下面的例子,使用 Lambda 实现一个 Runable 线程。

package org.lin;

public class LambdaTest {

    public static void main(String[] args) {
            new Thread(() -> System.out.println("run")).start();
    }
}

分析一下它的字节码:

// access flags 0x21
public class org/lin/LambdaTest {

  // compiled from: LambdaTest.java
  // access flags 0x19
  public final static INNERCLASS java/lang/invoke/MethodHandles$Lookup java/lang/invoke/MethodHandles Lookup

  // access flags 0x1
  public <init>()V
   L0
    LINENUMBER 3 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
    RETURN
   L1
    LOCALVARIABLE this Lorg/lin/LambdaTest; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x9
  public static main([Ljava/lang/String;)V
   L0
    LINENUMBER 6 L0
    NEW java/lang/Thread
    DUP
    INVOKEDYNAMIC run()Ljava/lang/Runnable; [
      // handle kind 0x6 : 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;
      // arguments:
      ()V, 
      // handle kind 0x6 : INVOKESTATIC
      org/lin/LambdaTest.lambda$main$0()V, 
      ()V
    ]
    INVOKESPECIAL java/lang/Thread.<init> (Ljava/lang/Runnable;)V
    INVOKEVIRTUAL java/lang/Thread.start ()V
   L1
    LINENUMBER 7 L1
    RETURN
   L2
    LOCALVARIABLE args [Ljava/lang/String; L0 L2 0
    MAXSTACK = 3
    MAXLOCALS = 1

  // access flags 0x100A
  private static synthetic lambda$main$0()V
   L0
    LINENUMBER 6 L0
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "run"
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
    RETURN
    MAXSTACK = 2
    MAXLOCALS = 0
}

关键指令

  INVOKEDYNAMIC run()Ljava/lang/Runnable; [
      // handle kind 0x6 : 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;
      // arguments:
      ()V, 
      // handle kind 0x6 : INVOKESTATIC
      org/lin/LambdaTest.lambda$main$0()V, 
      ()V
    ]

通过调用一个 INVOKEDYNAMIC 指令 ,将方法链接到这个方法上 org/lin/LambdaTest.lambda$main$0()V

通过 jclasslib 工具查看,我们看到多了一个内部类(VM Anonymous Class):
在这里插入图片描述
在运行时加上命令得到动态运行时的内部类 ,也就是 VM Anonymous Class 。

-Djdk.internal.lambda.dumpProxyClasses=<path>

生成的 VM Anonymous Class

// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.lin;

// $FF: synthetic class
final class LambdaTest$$Lambda$1 implements Runnable {
    private LambdaTest$$Lambda$1() {
    }

    public void run() {
        LambdaTest.lambda$main$0();
    }
}

该类 实现了 Runnable 接口,并在 run 方法上调用了 LambdaTest.lambda$main$0();

也就是上面字节码,动态生成的方法(desugaring method),该方法是经过 Lambda 语法,进行 脱糖分析 生成在字节码的内容。

 // access flags 0x100A
  private static synthetic lambda$main$0()V
   L0
    LINENUMBER 6 L0
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "run"
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
    RETURN
    MAXSTACK = 2
    MAXLOCALS = 0

原理总结

Lambda 利用 Invokedynamic 指令把调用和解析分离。

利用 invokedynamic 指令本身的特性(引导方法),把 Lambda 表达式在字节码文件的表示和真正的解析分离;
Lambda 表达式的链接解析发生运行期而非编译期,由 invokedynamic 的引导方法执行,扩展性强。

Lambda 是面向方法接口编程,利用 JDK8 提供的语法糖,因为对非方法引用的 Lambda 表达式,编译器都会为其生成一个方法实现 Lambda 表达式的逻辑,并出现在编译后的字节码文件中。这个方法的生成是 Lambda 表达式解析的关键,
也就是上面我们看到的 LambdaTest.lambda$main$0();* 方法。

当编译器遇到 lambda 表达式时,它首先将 lambda 主体脱糖(desugar)为一个方法,该方法的参数列表和返回类型与 lambda 表达式匹配,可能还有一些额外的参数(对于从词法范围捕获的值,如果有的话)。
在捕获 lambda 表达式的位置,它会生成一个invokedynamic callSite,调用时,会返回 lambda表达式转换为的 实现 Functional interface的实例。
此callSite 就是给出lambda实例 的 lambdaFactory, lambdaFactory 的动态参数是从词法范围捕获的值。启动 lambdaFactory 的方法是jdk的一个标准类 lambdaMetaFactory 提供的. 它在编译时获取当前 lambda 的已知信息并作为入参。

Lambda 运行的主要流程:

p2

性能分析

从一份 oracle 官方的性能对比数据来看, Lambda 和匿名内部类性能对比。
Lambda 的 热启动效率比匿名内部类性能要稍微差一点,而 Lambda 的 冷启动效率比匿名内部类却高很多。
所谓热启动是包括生成匿名类字节码并生成 class 的过程。
对相同 Lambda 表达式来说,热启动过程只发生一次,后面都是调用工厂方法即可,直接调用已有的匿名类称之为冷启动。
由此可以得出,Lambda 的性能要比匿名内部类要高很多。

p1

参考:
https://zhuanlan.zhihu.com/p/28124632
https://www.jianshu.com/p/d74e92f93752
https://www.jianshu.com/p/7ef49246c176
https://www.cnblogs.com/wzqshb/p/16987548.html
https://www.oracle.com/technetwork/java/jvmls2013kuksen-2014088.pdf

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值