有人可以解释o2
的行为吗?是由于编译器优化吗?它记录在JLS的某个地方吗?
public class Test {
public static void main(String[] args) {
Object o1 = new Object() {
String getSomething() {
return "AAA";
}
};
// o1.getSomething(); // FAILS
String methods1 = Arrays.toString(o1.getClass().getMethods());
var o2 = new Object() {
String getSomething() {
return "AAA";
}
};
o2.getSomething(); // OK
String methods2 = Arrays.toString(o2.getClass().getMethods());
System.out.println(methods1.equals(methods2));
}
}
产生的输出是
true
[UPDATE]
经过一些富有成效和有用的讨论,我认为我可以理解这种行为(如果我的假设是错误的,请发表评论。
首先,感谢@ user207421,他解释说Java编译器将o2
的类型与RHS相同,这是:
Object
getSomething
方法然后感谢@ Joachim Sauer指出了JLS中的适当位置。
一些其他相关的JLS引号:
[局部变量的类型是T相对于T提到的所有合成类型变量的向上投影(第4.10.5节。]
在确定变量的类型时,向上投影将应用于初始化程序的类型。如果初始化程序的类型包含捕获变量,则此投影将初始化程序的类型映射到不包含捕获变量的超类型。
尽管有可能允许变量的类型提及捕获变量,但通过将它们投影开来,我们强制执行一个吸引人的不变式,即捕获变量的范围永远不比包含捕获其类型的表达式的语句大。非正式地,捕获变量不能“泄漏”到后续语句中。
问题:在问题的背景下,我们能否说“捕获变量”也指getSomething()
?
最后,感谢@ Slaw指出getSomething
被声明为包私有,因此getMethods
没有返回它。
感谢任何评论/纠正。
Object
没有方法getSomething
。并且由于o1
的类型为Object
,所以编译器将不允许您调用o1.getSomething
。
对于o2
,变量的类型是您在初始化期间创建的匿名内部类型。该类型具有getSomething
方法,因此编译器将允许您调用它。
有趣的是,具有命名类型不能直接执行此操作。在o2
的声明中没有使用任何类型名称来获得相同的效果,因为该类型是匿名的。
在JLS 14.4.1 Local Variable Declarators and Types中定义。特别是这部分:
如果LocalVariableType为var,则当T被视为初始化表达式的类型时,将其视为未出现在赋值上下文中,因此为独立表达式(第15.2节)。
甚至在下面的示例中都可以看到:
var d = new Object() {}; // d has the type of the anonymous class
在JEP 286: Local-Variable Type Inference状态中表示为不可定义类型的部分:
匿名类类型不能命名,但是很容易理解的-它们只是类。允许变量具有匿名类类型引入了用于声明单例的有用速记本地类的实例。我们允许他们。
因此,考虑到创建了类实例,并且允许使用var
调用的方法进行编译,并且推断作为匿名类,进一步允许该方法被调用。
规范的Local Variable Declarators and Type部分以及示例也将其作为附带说明:
var d = new Object() {}; // d has the type of the anonymous class
请注意,某些用var声明的变量不能用显式类型,因为变量的类型不可表示。
[另一方面,在第一个实例中,您尝试执行的操作看起来像Invoking a method of an anonymous class,由于o1
的类型被推断为Object
,而且没有进一步的方法,该操作失败了。 getSomething
。如果您要调用方法getSomething
并在那里进行编译,则可以使用
Object o1 = new Object() {
String getSomething() {
System.out.println("something happened");
return "AAA";
}
}.getSomething();