pai*_*msn 1 java lambda garbage-collection
我阅读了https://www.baeldung.com/java-lambda-effectly-final-local-variables和许多文章(stackoverflow)但是,还有许多未解答的问题。
Supplier<Integer> incrementer(int start) {
return () -> start++;
}
// start is a local variable, and we are trying to modify it inside of a lambda expression.
Run Code Online (Sandbox Code Playgroud)
他们说
好吧,请注意我们正在从我们的方法中返回 lambda。因此,直到 start 方法参数被垃圾回收之后,lambda 才会运行。Java 必须复制 start 才能使该 lambda 存在于该方法之外。
start变量的生命周期是incrementer().它们都存在于同一个堆栈上并且具有共同的生命周期。但我不明白为什么它说GC并且它不运行。
Concurrency Issues。由于堆栈是为每个线程分配的,因此不会出现并发问题。相反,当静态成员变量会导致并发问题时,为什么局部变量需要是final的呢?
变量的捕获与并发执行或其安全性完全无关,原因完全不同。
在回答你的问题之前,我先解释一下什么是 lambda 表达式。
当您使用 lambda 表达式时,在编译和运行时都会发生一些对开发人员隐藏的事情。lambda 表达式是 java 语言的一部分也是毫无价值的,它不存在于生成的字节码中。
我将使用以下代码作为示例
public class GreeterFactory {
private String header = "Hello ";
public Function<String, String> createGreeter(int greeterId){
Function<String, String> greeter = username -> {
return String.format("(%s) %s: %s", greeterId, header, username);
};
return greeter;
}
}
Run Code Online (Sandbox Code Playgroud)
当 javac 将 java 编译为字节码时,它会将 lambda 的主体转换为嵌入类中的新方法(这就是 lambda 表达式可以被视为匿名方法的原因)。
以下是字节码中的内容(使用 javap 工具反编译):
Compiled from "GreeterFactory.java"
public class various.GreeterFactory {
private java.lang.String header;
public various.GreeterFactory();
public java.util.function.Function<java.lang.String, java.lang.String> createGreeter(int);
private java.lang.String lambda$createGreeter$0(int, java.lang.String);
}
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,该类GreeterFactory不仅具有createGreeter我编写的方法。现在它还将具有lambda$createGreeter$0由编译器生成的方法。
您可能会注意到的一件事是生成的方法有两个参数(int 和 String),即使在我的 lambda 中我只声明了一个参数 - String。这样做的原因是因为在运行时,不仅会使用我传递的参数(当我执行apply接口形式的方法时Function)调用该方法,还会使用所有“捕获”的值来调用该方法。这就引出了第二点:
我们已经知道 lambda 被转换为实际的方法,现在的问题是:执行该 lamda 表达式时我到底得到了什么结果(除了它是实现Function接口的事实之外)?
该Function<String, String> greeter变量实际上指向一个内部对象:
this GreeterFactory(以便稍后可以调用该对象的方法)greeterId)lambda$createGreeter$0方法当您在调试器中检查该对象时,您可以看到它。您将看到以下内容:

请注意,greeterobject 恰好具有我提到的这两个值(对 object 的引用和从 复制的this GreeterFactory值)。这正是 lambda 表达式中“捕获”的含义。23greeterId
稍后,当apply在此对象上执行时,它实际上会使用所有捕获的值+传递给方法的参数来调用对象上的lambda$createGreeter$0方法。this GreeterFactoryapply
我希望我已经在上面解释了“捕获”是什么以及它是如何工作的。让我们进入最终/实际上的最终点。
免责声明:我没有找到任何有关它的官方信息,这只是我的假设,因此:我可能是错的。
请注意,lambda 仅存在于 java 语言级别,而不存在于字节码上。在解释了 lambda 的工作原理(生成新方法)之后,我认为在技术上也可以捕获非有效最终变量。
我认为lambda表达式的设计者选择这种方式的原因是为了帮助开发人员编写无错误的代码。
如果捕获的变量不是有效的最终变量,这意味着:它们可以在 lambda 之外以及 lambda 内进一步修改,从开发人员的角度来看,这可能会导致许多混乱和误解,从而有效地导致许多错误。即开发人员可能期望更改 lambda 内的变量值应该会影响外部方法范围内的该变量(这是因为它在 lambda 体内的语言中不可见,我们实际上位于新生成的方法的范围内),或者他们可能期望相反的情况。简而言之:一片混乱。
我认为这就是这一决定背后的原因,也是编译器和语言强制执行这一决定的原因,即将 lambda 的作用域和嵌入方法作用域视为一个(即使在运行时它们是不同的作用域)。
请注意,以前对于匿名类捕获的变量也是如此,因此开发人员已经熟悉这种方法。
为什么lambda可以自由修改对象中的字段?因为它只是该对象的类中的一个方法,并且与任何其他方法一样,它可以自由访问其所有成员。期待不同的行为会令人困惑。
| 归档时间: |
|
| 查看次数: |
1212 次 |
| 最近记录: |