为什么 Object 和 var 变量的行为不同?

Bar*_*jeu 7 java var type-inference java-10

有人可以解释的行为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));
    }    
}
Run Code Online (Sandbox Code Playgroud)

产生的输出是

真的

[更新]

经过一些富有成效和有用的讨论,我想我可以理解这种行为(如果我的假设有误,请发表评论)。

首先,感谢@user207421,他解释说 Java 编译器将类型o2视为与 RHS 相同的类型,其中:

  • 延伸 Object
  • getSomething方法

然后感谢@Joachim Sauer,他指出了 JLS 中的正确位置。

一些更相关的 JLS 报价:

局部变量的类型是 T 相对于 T 提到的所有合成类型变量的向上投影(第 4.10.5 节)。

在确定变量的类型时,向上投影应用于初始化器的类型。如果初始化器的类型包含捕获变量,则此投影将初始化器的类型映射到不包含捕获变量的超类型。

虽然允许变量的类型提及捕获变量是可能的,但通过将它们投射出去,我们强制执行了一个有吸引力的不变量,即捕获变量的范围永远不会大于包含其类型被捕获的表达式的语句。非正式地,捕获变量不能“泄漏”到后续语句中。

问题:我们可以说“捕获变量”getSomething()在问题的上下文中也指代吗?

最后,感谢@Slaw指出它getSomething被声明为私有包,因此没有被getMethods.

任何评论/更正表示赞赏。

Joa*_*uer 5

Object没有办法getSomething。由于o1是类型Object,编译器不允许您调用o1.getSomething.

如果o2变量的类型是您在初始化期间创建的匿名内部类型。该类型有一个getSomething方法,因此编译器将允许您调用它。

有趣的是,这是您无法通过命名类型直接完成的事情。没有在声明中使用的类型名称o2来获得相同的效果,因为该类型是匿名的。

它在JLS 14.4.1 局部变量声明符和类型中定义。具体这部分:

如果 LocalVariableType 是 var,则在将 T 视为初始化表达式的类型时,将其视为未出现在赋值上下文中,因此是独立表达式(第 15.2 节)。

甚至还有一个例子说明如下:

var d = new Object() {};  // d has the type of the anonymous class
Run Code Online (Sandbox Code Playgroud)

  • @Barat该方法可通过反射检索。在你的问题中,你调用 [`#getMethods()`](https://docs.oracle.com/en/java/javase/13/docs/api/java.base/java/lang/Class.html#getMethods( )) 只返回 _public_ 方法。如果您要使用 [`#getDeclaredMethods()`](https://docs.oracle.com/en/java/javase/13/docs/api/java.base/java/lang/Class.html#getDeclaredMethods( )) 然后你就会看到你正在寻找的方法。该文档更详细。 (4认同)
  • @BaratSahdzijeu:当您调用“getClass()”时,您会查看*变量引用的对象的实际类型*。这**不**依赖于保存引用的变量的类型。 (3认同)

Nam*_*man 5

JEP 286: Local-Variable Type Inference 中表示为不可表示类型的部分指出:

匿名类类型无法命名,但它们很容易理解——它们只是类。允许变量具有匿名类类型为声明本地类的单例实例引入了一个有用的速记。我们允许他们。

因此,var考虑到类实例被创建并推断为匿名类,允许您使用 调用的方法进行编译,从而进一步允许调用该方法。

规范的局部变量声明符和类型部分也将其作为旁注与示例一起提及:

var d = new Object() {};  // d has the type of the anonymous class
Run Code Online (Sandbox Code Playgroud)

请注意,某些用 var 声明的变量不能用显式类型声明,因为变量的类型是不可表示的。

另一方面,对于第一个实例,您尝试执行的操作看起来像调用匿名类的方法,由于o1推断出的类型为Object并且进一步没有称为 的方法,因此失败getSomething。如果您要调用该方法getSomething并在那里修复编译,则可以使用

Object o1 = new Object() {
  String getSomething() {
    System.out.println("something happened");
    return "AAA";
  }
}.getSomething();
Run Code Online (Sandbox Code Playgroud)

  • 我建议去 JLS 获取确切的规范,JEP 是将其包含在 JLS 中的过程,有时措辞可能有所不同...... (3认同)
  • @JoachimSauer我可能已经来不及编辑了(我可以看到你的答案中引用的JLS),但我引用了赞美你的答案的那句话,我希望它值得保留作为未来读者的注释。 (2认同)