Java 奇怪*&引用;使用eclipse编译器编译时LocalVariableTypeTable中的项
让我们使用EclipseMars.2捆绑包中的ECJ编译器编译以下代码:Java 奇怪*&引用;使用eclipse编译器编译时LocalVariableTypeTable中的项,java,eclipse,lambda,bytecode,ecj,Java,Eclipse,Lambda,Bytecode,Ecj,让我们使用EclipseMars.2捆绑包中的ECJ编译器编译以下代码: import java.util.stream.*; public class Test { String test(Stream<?> s) { return s.collect(Collector.of(() -> "", (a, t) -> {}, (a1, a2) -> a1)); } } 看到这个,我非常惊讶*本地变量类型表中的条目。JVM规范Lo
import java.util.stream.*;
public class Test {
String test(Stream<?> s) {
return s.collect(Collector.of(() -> "", (a, t) -> {}, (a1, a2) -> a1));
}
}
看到这个,我非常惊讶*代码>本地变量类型表中的条目
。JVM规范LocalVariableTypeTable属性并说明:
该索引处的constant\u pool
项必须包含一个constant\u Utf8\u info
结构(),该结构表示对源程序中局部变量类型进行编码的字段签名()
定义字段签名的语法,如果我理解正确,它不包括任何类似于的内容*代码>
还应该注意的是,无论是javac编译器还是较旧的ECJ 3.10.x版本都不会生成这个LocalVariableTypeTable
条目。是*代码>一些非标准的Eclipse扩展,或者我在JVM规范中遗漏了什么?这是否意味着ECJ不符合JVM规范?什么*
实际上是指,是否有任何其他类似的字符串可以出现在LocalVariableTypeTable
属性中?标记代码>由ecj用于在通用签名中对捕获类型进行编码。因此*代码>表示捕获无限通配符
在内部,ecj使用了两种风格的CaptureBinding
,一种用于实现所谓的“新鲜类型变量”,另一种用于实现(使用与“自由类型变量”相同的行话)。两者都使用生成签名代码>。仔细看,在本例中,我们有一个“旧式”捕获:t
具有类型capture#1-of?
,在流中捕获
问题是:似乎没有为这些新类型变量定义编码(在其他属性中,这些新类型变量在源代码中没有对应关系,因此没有名称)
我无法让javac
为lambda发出任何LocalVariableTypeTable
,因此他们可能只是避免回答这个问题
既然两个编译器都同意推断捕获的t
,为什么一个编译器会生成LVTT,而另一个编译器不会?有这个吗
此差异仅对类型使用类型变量或参数化类型的变量显著
根据JLS,捕获是新类型变量,因此LVTT条目很重要,JVM中没有为这种类型指定格式是一个遗漏
后果
上面只描述和解释了现状,说明没有任何规范告诉编译器与当前状态不同的行为。显然,这不是一个完全可取的情况
有人可能想联系Oracle,提到Java8引入了JVM部分未涉及的情况。一旦局部变量受到类型推断的影响,这种情况可能变得更加相关
任何观察到当前局势的负面影响的人都被邀请插话,否则这一点就没有什么优先权
更新:
同时,有人报告了一个错误,其中需要一个常规的签名属性(不能随意省略)来编码一个不能根据JVM编码的类型。那样的话。根据a,但我认为这场讨论还没有结束(无可否认,JLS尚未确保这一目标)
更新2:
在收到规范作者的建议后,我看到了最终解决方案的三个部分:
(1) 任何字节码属性中的每个类型签名都必须遵守JVM 4.7.9.1中的语法。两个ecj都不是
和javac的
都是合法的
(2) 编译器应该在不存在合法编码的情况下近似类型签名,例如,使用擦除而不是捕获。对于LVTT条目,此类近似值应视为合法
(3) JLS必须确保只有使用JVM 4.7.9.1可编码的类型出现在必须生成签名属性的位置
对于ecj的未来版本,已修改了第(1)项和第(2)项。我不能谈论javac和JLS将被相应修复的时间表。它可能与那些bug和。这些“新类型变量”有助于验证Collector.of
方法的泛型调用的正确性,但我看不出它们为什么会出现在局部变量表中。参数t
不是泛型,它只是Object
,因为这是在该上下文中唯一有效的类型。值得注意的是,t
是合成方法的一个参数,因此,如果t
不是普通的对象
,ECJ必须提供一个描述通用参数的通用方法签名,但它没有。参见:“如果T是通配符参数化功能接口类型,且lambda表达式为隐式类型,则地面目标类型为T的非通配符参数化(§9.9)”,并且在§9.9末尾:“有时,可以从上下文(如lambda表达式的参数类型)中知道拟使用哪种函数类型(§15.27.3)。有时,需要选择一种;在这些情况下,使用边界。”一般来说,变量有一个真正的类型,而不是像CAP#1
那样只存在于编译器中。因此JVM没有办法对这种非类型的东西进行编码也就不足为奇了。局部变量表除了调试之外没有其他用途,那么告诉调试器变量的类型有什么意义呢是CAP#1
而不是?
还是仅仅是对象
?既然讨论形式规范真的超出了SO的范围,那么让我们关注一个实际问题来结束这个问题:既然LocalVariableTypeTable
仅仅是为了调试目的而存在的:Eclipse会消除什么
private static void lambda$1(java.lang.String, java.lang.Object);
descriptor: (Ljava/lang/String;Ljava/lang/Object;)V
flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
Code:
stack=0, locals=2, args_size=2
0: return
LineNumberTable:
line 5: 0
LocalVariableTable:
Start Length Slot Name Signature
0 1 0 a Ljava/lang/String;
0 1 1 t Ljava/lang/Object;
LocalVariableTypeTable:
Start Length Slot Name Signature
0 1 1 t !*