为啥 Object 和 var 变量的行为不同?
Posted
技术标签:
【中文标题】为啥 Object 和 var 变量的行为不同?【英文标题】:Why do Object and var variables behave differently?为什么 Object 和 var 变量的行为不同? 【发布时间】:2020-04-04 12:53:46 【问题描述】:有人可以解释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));
产生的输出是
是的
[更新]
经过一些富有成效和有用的讨论后,我想我可以理解这种行为(如果我的假设是错误的,请发布 cmets)。
首先,感谢 @user207421,他解释说 Java 编译器将 o2
的类型视为与 RHS 相同,其中:
Object
有getSomething
方法
然后感谢 @Joachim Sauer 指出了 JLS 中的正确位置。
更多相关的 JLS 引用:
局部变量的类型是 T 相对于 T 提到的所有合成类型变量的向上投影(第 4.10.5 节)。
在确定变量的类型时,向上投影应用于初始化器的类型。如果初始化器的类型包含捕获变量,则此投影将初始化器的类型映射到不包含捕获变量的超类型。
虽然允许变量的类型提及捕获变量是可能的,但通过将它们投影出来,我们强制执行一个有吸引力的不变量,即捕获变量的范围永远不会大于包含其类型被捕获的表达式的语句。非正式地,捕获变量不能“泄漏”到后续语句中。
问题:我们可以说“捕获变量”在问题的上下文中也指的是getSomething()
吗?
最后,感谢 @Slaw 指出getSomething
被声明为私有包,因此getMethods
没有返回。
感谢任何 cmets/更正。
【问题讨论】:
这是因为o1
的类型显式声明为Object
,而o2
的类型与RHS相同,extends Object
并且有额外的方法,所以你可以调用它。
@user207421 我喜欢你的解释“RHS”,这可以更详细地显示如下:new Object() String getSomething() return "AAA"; .getSomething();
但我很想看看它在哪里被清楚地记录了。
您完全歪曲了我在回答中所说的话,因此它根本不符合我所说的。您对编译器实际执行的操作的误解也不正确。它所做的是将o2
的type 指定为与RHS 的type 相同,这正是var
所做的。这在 JLS 中有记录。
@user207421 抱歉,它确实表达了我相信你并没有说的话,当我制作演示代码 sn-p 时,我的意思是概念上的,而不是字面意思。无论如何,我再次更新了问题。
另见this Q&A。
【参考方案1】:
Object
没有方法getSomething
。由于o1
是Object
类型,编译器将不允许您调用o1.getSomething
。
在o2
的情况下,变量的类型是您在初始化期间创建的匿名内部类型。该类型有一个getSomething
方法,因此编译器将允许您调用它。
有趣的是,这是通过命名类型无法直接完成的。您在 o2
的声明中没有使用类型名称来获得相同的效果,因为该类型是匿名的。
在JLS 14.4.1 Local Variable Declarators and Types 中定义。具体这部分:
如果 LocalVariableType 是 var,那么当 T 被视为没有出现在赋值上下文中时,令 T 为初始化表达式的类型,因此它是一个独立的表达式(第 15.2 节)。
下面还有一个例子说明:
var d = new Object() ; // d has the type of the anonymous class
【讨论】:
我希望得到更多技术性的答案,两者都是 内部匿名类型。我的问题是为什么编译器不抱怨 o2(我确定它为什么抱怨 o1) @BaratSahdzijeu:我不太明白:o1
的类型是Object
,而Object
没有getSomething
方法。这几乎是技术性的......o2
的类型是 not Object
,但是您在其初始化时创建的未命名类型......不同之处在于 类型变量不在分配给它的值的类型!
当然我理解这种行为导致了Object
变量类型与var
类型名称。你能解释一下为什么在这种情况下,两个对象的方法比较相同,而 btw getSomething
不在其中吗?
@BaratSahdzijeu:当您调用getClass()
时,您会查看变量引用的对象的实际类型。这不取决于保存引用的变量的类型。
@Barat 该方法可通过反射检索。在您的问题中,您调用 #getMethods()
仅返回 public 方法。如果您要使用#getDeclaredMethods()
,那么您会看到您正在寻找的方法。文档更详细。【参考方案2】:
JEP 286: Local-Variable Type Inference 状态中表示为 Non-Denotable Types 的部分:
匿名类类型不能命名,但它们很容易命名 理解——它们只是类。允许变量具有匿名性 类类型为声明单例引入了一种有用的简写 本地类的实例。我们允许他们。
因此,考虑到已创建类实例并推断作为匿名类进一步允许调用该方法,允许使用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();
【讨论】:
我建议去 JLS 了解确切的规范,JEP 是将其包含在 JLS 中的过程,有时措辞可能会有所不同...... @JoachimSauer 肯定会在一些空闲时间做到这一点。现在,引用我记得读过的内容。 @JoachimSauer 我可能编辑得太晚了(我可以看到您的回答中引用了 JLS),但我引用了与您的回答相得益彰的行,我希望它值得作为注释供未来的读者阅读。以上是关于为啥 Object 和 var 变量的行为不同?的主要内容,如果未能解决你的问题,请参考以下文章