概述
在Java 1.8中引入的 lambda表达式 (又称闭合,匿名函数)是代码清洁度,降低重复和功能的重要语法。 p>
它提供了语言及其兄弟姐妹,例如Kotlin和Scalaâ,具有将功能作为值存储的能力,然后也可以用于实现高阶函数,即采用或返回其他功能的功能。
但是它如何在表面下方工作?
功能接口
由于java是一种静态类型的语言,因此lambda需要具有类型。 lambda的类型包括其参数和返回类型,可以使用函数接口(例如Function<String, String>
)看到,类似于String function(String)
。
是什么使功能性接口如此特殊?
它们有一个限制:它们可能包含多个默认方法或静态方法,但仅包含一个抽象方法。这就是为什么有时将功能接口称为“ SAM(单个抽象方法)接口”的原因。
此限制之所以存在,恰恰是因为编译器需要知道将实现哪种方法来创建lambda,例如,Supplier
中的get
或Function
中的apply
。使用多个抽象方法,在编写lambda时可以实现其中任何一个。
自定义功能接口的小示例:
// The @FunctionalInterface annotation is
// used to tell the compiler to enforce the SAM restriction.
@FunctionalInterface
interface TokenProvider {
ApiToken get();
}
低级
为了谈论lambdas 真正的工作方式,我们需要简要说明 java虚拟机(特此称为JVM)。 p>
当编译基于JVM的语言(Java,Kotlin等)时,它会输出带有.Class扩展名的文件。类文件包含所谓的 bytecode。 bytecode是一个术语,用于描述由虚拟机而不是CPU执行的原始字节制成的任何编译代码。 虚拟机(不要与VirtualBox,VMware,QEMU等混淆)是一个处理和执行字节码的程序,其可选步骤将字节码转换为优化的机器代码,该代码在CPU上运行。每台虚拟机都处理不同的字节码语言,即JVM处理JVM字节码,该字节专门为Java平台设计,并包含有关类,字段,方法等的信息。 在JVM中,代码分为单用功能,称为“指令”。说明具有“助记符”和一组参数,助记符是该字节语言中该特定指令的名称。 对于一个简单的示例: 这是带有助记符 编译代码时,它将变成指令列表。当您的代码运行时,JVM从第一个指令开始,然后向下逐渐下降,直到达到末端或达到跳跃(如if语句),这使JVM跳到另一个指令并开始阅读列表。<<<<<<<<<<<<<<<<<<< /p>
您可能会惊讶地发现,您在课堂上声明的每个lambda的主体实际上成为该课程中的一种私人方法。这些Lambda主体方法由编译器生成,并包含放置在Lambda主体中的所有代码。 请记住所有这些!以后将非常重要。 Java的调用API (koude10)在Java 1.7中引入了。它允许您创建对方法或构造函数的引用的"method handles,"。这些与反射API不同,最著名的差异是在方法句柄创建上执行访问检查,而不是在每个呼叫上进行反射。
字节码说明
ldc "Hello, World!"
ldc
的单个指令,缩短了“负载常数”。
运行时,它采用第一个参数(必须是某种常数值,例如字符串,int或float),然后将其推到“操作数堆栈”上,这是用于在值执行操作的堆栈数据结构。
Lambda的身体
他们遵循特定的命名计划:lambda$<originating method>$<index>
因此,您的main
方法中的第一个lambda将使其身体成为lambda$main$0
方法。
方法处理和
java.lang.invoke
调用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的意外复杂性和动态调用的出色设计所强迫,因此我觉得有必要以(某种程度上)易于消化的方式分享此信息。
我希望您发现这个有趣,有趣或有用。再见!