深入研究兰巴斯
#编程 #写作 #java

概述

在Java 1.8中引入的 lambda表达式 (又称闭合,匿名函数)是代码清洁度,降低重复和功能的重要语法。

它提供了语言及其兄弟姐妹,例如Kotlin和Scalaâ,具有将功能作为值存储的能力,然后也可以用于实现高阶函数,即采用或返回其他功能的功能。

但是它如何在表面下方工作?

功能接口

由于java是一种静态类型的语言,因此lambda需要具有类型。 lambda的类型包括其参数和返回类型,可以使用函数接口(例如Function<String, String>)看到,类似于String function(String)

是什么使功能性接口如此特殊?

它们有一个限制:它们可能包含多个默认方法或静态方法,但仅包含一个抽象方法。这就是为什么有时将功能接口称为“ SAM(单个抽象方法)接口”的原因。

此限制之所以存在,恰恰是因为编译器需要知道将实现哪种方法来创建lambda,例如,Supplier中的getFunction中的apply。使用多个抽象方法,在编写lambda时可以实现其中任何一个。

自定义功能接口的小示例:

// The @FunctionalInterface annotation is
// used to tell the compiler to enforce the SAM restriction.
@FunctionalInterface
interface TokenProvider {
    ApiToken get();
}

低级

为了谈论lambdas 真正的工作方式,我们需要简要说明 java虚拟机(特此称为JVM)。

当编译基于JVM的语言(Java,Kotlin等)时,它会输出带有.Class扩展名的文件。类文件包含所谓的 bytecode。 bytecode是一个术语,用于描述由虚拟机而不是CPU执行的原始字节制成的任何编译代码。

虚拟机(不要与VirtualBox,VMware,QEMU等混淆)是一个处理和执行字节码的程序,其可选步骤将字节码转换为优化的机器代码,该代码在CPU上运行。每台虚拟机都处理不同的字节码语言,即JVM处理JVM字节码,该字节专门为Java平台设计,并包含有关类,字段,方法等的信息。

字节码说明

在JVM中,代码分为单用功能,称为“指令”。说明具有“助记符”和一组参数,助记符是该字节语言中该特定指令的名称。

对于一个简单的示例:

ldc "Hello, World!"

这是带有助记符ldc的单个指令,缩短了“负载常数”。
运行时,它采用第一个参数(必须是某种常数值,例如字符串,int或float),然后将其推到“操作数堆栈”上,这是用于在值执行操作的堆栈数据结构。

编译代码时,它将变成指令列表。当您的代码运行时,JVM从第一个指令开始,然后向下逐渐下降,直到达到末端或达到跳跃(如if语句),这使JVM跳到另一个指令并开始阅读列表。<<<<<<<<<<<<<<<<<<< /p>

Lambda的身体

您可能会惊讶地发现,您在课堂上声明的每个lambda的主体实际上成为该课程中的一种私人方法。这些Lambda主体方法由编译器生成,并包含放置在Lambda主体中的所有代码。
他们遵循特定的命名计划:lambda$<originating method>$<index>
因此,您的main方法中的第一个lambda将使其身体成为lambda$main$0方法。

请记住所有这些!以后将非常重要。

方法处理和java.lang.invoke

Java的调用API koude10)在Java 1.7中引入了。它允许您创建对方法或构造函数的引用的"method handles,"。这些与反射API不同,最著名的差异是在方法句柄创建上执行访问检查,而不是在每个呼叫上进行反射。

调用API提供的另一种类型是koude12,它具有方法手柄。尽管这很简单,但CallSite的原因很重要。

invokedynamic

lambdas的核心组成部分是 invokedynamic 指令。它像所有其他invoke-指令一样,调用/调用方法。

但是,它与其他invoke-指令之间的区别是,invokedynamic首次运行,它将调用“ bootstrap方法”。这是一种返回CallSite对象的方法,该对象“绑定”到该invokedynamic指令,这意味着只需要一次检索一次。检索CallSite后,调用其基础方法句柄,并假设方法句柄返回值,则将返回的值推到操作数堆栈。

此指令允许我们从本质上接收函数,这些功能在避免性能障碍和兼容性问题的同时被调用。

但是,invokedynamic不是难题的唯一部分。

Lambda Metafactory

java.lang.invoke下,存在一个高度低级的阶级,称为koude24。此类用于创建返回“函数对象”的方法处理,该操作是实现功能接口的对象。

这样做的方式有些复杂,所以请忍受。

LambdaMetafactory具有名为koude26的函数,该函数在给出以下参数后返回CallSite(仅用于简单的参数):

  • 实现的方法的名称,例如在实现Supplier时“ get”。
  • 要实现的方法的描述符,意思是返回类型和参数类型。对于Supplier<String>的方法,将有String返回类型,但没有参数类型。
  • CallSite的描述符,其中参数类型是捕获的本地变量,返回类型是应实现的接口。
  • 实现方法的句柄,该方法在接口的方法中被调用。这相当于兰伯达的身体。

最有趣的部分是该方法处理Lambda Metagactory创建的方法。

it 在运行时生成字节码。 this thiseTecode实例化了一个新的函数对象,该对象使用任何必要的参数调用lambda body方法,然后创建一个新的方法句柄,该方法调用该字节字节码,然后在将其返回之前返回该字节。致电站点。

bytecode生成是通过名为ObjectWeb ASM的第三方库来完成的,该库使您可以轻松地解析和编写JVM字节码。标准库在这种情况下由于其性能和功能完整而使用。

最后,这些函数对象之一的真实示例:

final class Test$$Lambda$1 implements Supplier<String> {
    private final String arg$1;

    private Test$$Lambda$1(String arg$1) {
        this.arg$1 = arg$1;
    }

    public String get() {
        return Test.lambda$main$0(arg$1);
    }
}
// Oh no, it's hideous.

该函数对象是从以下代码生成的:

public class Test {
    public static void main(String[] args) {
        String value = "Hello, World!";
        Supplier<String> msg = () -> value;
        msg.get();
    }
}

结尾

基本上就是Lambdas的创建和调用。我个人被Java的Lambdas的意外复杂性和动态调用的出色设计所强迫,因此我觉得有必要以(某种程度上)易于消化的方式分享此信息。

我希望您发现这个有趣,有趣或有用。再见!