【发布时间】:2020-04-14 04:28:08
【问题描述】:
在回答this question 关于捕获局部变量的 lambda 时,我定义了一个简单的 lambda 来捕获一个局部变量,并表明 lambda 有一个包含该变量值的字段。根据各种来源(例如here、here),当 lambda 捕获局部变量时,其值存储在“合成”字段中。 Java 虚拟机规范 (§4.7.8) 似乎暗示了这一点,它说:
源代码中未出现的类成员必须使用 Synthetic 属性进行标记,否则必须设置其 ACC_SYNTHETIC 标志。此要求的唯一例外是编译器生成的方法,它们不被视为实现工件,即表示 Java 编程语言的默认构造函数的实例初始化方法(第 2.9.1 节)、类或接口初始化方法(第 2.9.2 节) ),以及 Enum.values() 和 Enum.valueOf() 方法。
lambda 的字段不是定义的异常之一,并且 lambda 的字段没有在源代码中声明,所以根据我的理解,该字段应该是根据这个规则合成的。
可以通过反射轻松证明场的存在。但是,当我使用Field.isSynthetic 方法检查时,它实际上返回false。该方法的文档说明了这一点:
如果此字段是合成字段,则返回 true;否则返回 false。
我正在 Java 10.0.1 中使用 JShell 进行测试:
> class A { static Runnable a(int x) { return () -> System.out.println(x); } }
| created class A
> Runnable r = A.a(5);
r ==> A$$Lambda$15/1413653265@548e7350
> import java.lang.reflect.Field;
> Field[] fields = r.getClass().getDeclaredFields();
fields ==> Field[1] { private final int A$$Lambda$15/1413653265.arg$1 }
> fields[0].isSynthetic()
$5 ==> false
同样的行为发生在 JShell 之外:
import java.lang.reflect.Field;
public class LambdaTest {
static Runnable a(int x) {
return () -> System.out.println(x);
}
public static void main(String[] args) {
Runnable r = a(5);
Field[] fields = r.getClass().getDeclaredFields();
boolean isSynthetic = fields[0].isSynthetic();
System.out.println("isSynthetic == " + isSynthetic); // false
}
}
对这种差异的解释是什么?我是否误解了 JVMS,我是否误解了 Field.isSynthetic 方法文档,规范和文档是否使用“合成”一词来表示不同的东西,或者这是一个错误?
【问题讨论】:
-
鉴于整个类都是合成的,也许他们认为没有必要将每个字段也标记为合成的。
-
@RealSkeptic 很有趣,也许就是这样,但 JVMS 似乎并没有为此留下回旋余地:它说 “必须标记” 合成的。跨度>
-
它提供了两种将其标记为合成的方法。请注意
isSynthetic返回的定义说它应该是合成的“根据 JLS”,而 JLS 正在谈论“合成构造”并且对此类构造的各种成员含糊其辞。 -
由于 JLS 只允许“构造”是合成的,而
isSynthetic文档说它根据 JLS 确定该字段是否是合成的,我认为这意味着该字段本身算作一个构造,而不仅仅是构造的成员。但我认为你可能会有所收获。 -
JLS 的其他地方暗示变量应该算作构造,例如“枚举常量的类体内的任何构造的明确赋值/取消赋值状态受类的常用规则控制。”
标签: java lambda closures language-lawyer java-10