"根据JLS,'T.super'是合法的表达式吗?"

19

考虑以下表达式集合:

class T {{
/*1*/   Object o = T.super; // error: '.' expected
/*2*/   o.toString();
}}

试图编译这段代码将在/*1*/行失败,并显示以下错误信息:

fail

error: '.' expected
    o = T.super;
               ^

当使用OpenJDK 1.8.0 (Ubuntu)Oracle JDK 1.8 (Windows)时,以下内容与之相关。

然而,Eclipse 4.5.0 (Mars)编译此内容时没有任何错误,并得到以下结果:

class T {
    T();
     0  aload_0 [this]
     1  invokespecial java.lang.Object() [8] // super()
     4  aload_0 [this]
     5  astore_1 [o]  // o = T.super
     7  invokevirtual java.lang.Object.toString() : java.lang.String [10]
    10  pop           // ^-- o.toString()
    11  return
}

从这里可以看到,Java代码的第/*1*/行(结果的第5行)正确地将this转换为Object(Eclipse对T.super的理解),存储在本地变量o中。当代码被执行时,它正常完成,并且/*2*/行产生了正确的结果。
到目前为止,我没有找到任何与o = T.super;相关的内容在Java 8语言规范中,即它是否合法。由于它没有明确说明它是一个合法的表达式,所以我的猜测是它是不合法的。但是,为什么Eclipse认为它是合法的呢?因此我的问题是:
根据JLS,T.super是一个合法的表达式吗?

Eclipse Mars 4.5.1可以编译它,但显然javac 1.8.0_66失败了。 - Tunaki
我不能代表JLS发言,但是如果toString被重写,编译后的代码仍应该打印T实例的toString - samczsun
JLS 15.11.2 只定义了 "super.identifier" 和 "T.super.identifier"。对我来说,这意味着 "T.super" 没有被定义(而且 "super" 不能作为标识符,因为它是一个关键字)。所以我会将 "T.super" 视为未定义(因此是编译时错误)。 - Joachim Sauer
双括号是用来做什么的?我有什么遗漏吗? - anon
1
@QPaysTaxes,这是一个初始化器。有时候大括号会放在这个位置,如果你没见过,可能很难识别。 - Sergei Tachenov
3
@QPaysTaxes 经常用来填充集合,比如:new ArrayList<>() {{add(something);}}。虽然这不是一种非常优雅的方式,因为它创建了一个不必要的匿名类,但仍然可以使用。 - Sergei Tachenov
4个回答

13
不是这样的。请参考第19章。搜索关键字super会得到以下结构:
  • 通配符边界:extends T/super T
  • 显式构造函数调用:super(args)
  • 字段访问:[Typename.]super.field
  • 方法调用:[Typename.]super.method()
  • 方法引用:super::method
它编译通过可能被认为是一个错误或语言扩展,尽管两者之间没有实质性的区别。

是的,我在整个JLS上进行了搜索,也没有找到相关的语法。但这是否意味着它是非法的,还是只是未指定的 - charlie
2
@charlie,第19章是完整的语法。如果不在那里,它就不是有效的语法。如果从技术上讲,它是由语法允许的,但在其他章节中没有描述,那么我们可以说它是未指定的。但事实上,它只是一个无效的语言结构。 - Sergei Tachenov
1
“语言扩展”本质上就是一个 bug。特别是在这种情况下,它没有任何好处。在任何编译 WhateverType x=Outer.super; 的场景中,符合标准的 WhateverType x=Outer.this; 都能完全达到相同的效果。 - Holger
1
@Holger,我同意。即使它做了像让x后来的行为像super(也就是说,真正影响方法解析),那么它会更加危险和令人困惑。所以当我说“错误或语言扩展”时,我基本上是指这些是相同事物的不同名称。不过最好编辑一下以澄清。 - Sergei Tachenov
@SergeyTachenov:好的,我会坚持这个。 - charlie
显示剩余2条评论

3

接受这种语法一直是Eclipse中一个长期存在的错误, 但已经在Eclipse 4.6的里程碑5中得到修复。


感谢修复!只有一个简短的问题:错误报告指出:“问题特定于字段初始化(在初始化程序或构造函数中)”。但至少在4.5.0M3中,它是代码中任何地方的合法赋值,例如public String toString() {Object o = ""; return (o = T.super).toString();}。所以为了确保,这个修复是否适用于所有这样的非法情况? - charlie
@charlie,关于字段初始化的陈述是一个误导。通过修复,T.super只能作为字段、方法调用或方法引用的接收者而被提及,因此我认为这个解决方案是完整的(我刚刚测试了你的toString()示例,在HEAD中正确地被拒绝)。 - Stephan Herrmann

3

T.super 不是合法的表达式,因为它没有意义。 super(带或不带明确类型)语法只用于从超类调用方法。如果T.super是合法的语法,则它不会调用任何方法,它只引用一个类实例。在您的情况下,它引用了outer T 实例。正确的语法应该是T.this,这将指向outer this

可以将您的类定义看作如下(具有命名的内部类):

class Outer
{
    // Constructor Body
    {
        class Inner
        {{
            /*1*/   Outer o = Outer.super; // error: '.' expected
            /*2*/   o.toString();
        }};
        new Inner();
    }
}

正确引用外部类的方法应该是:
Outer o = Outer.this; // valid

1
@charlie:这个讨论是浪费资源。由于Outer.super不是有效的表达式,所以说它具有类型Object是没有意义的。构造Outer.super.identifier可以用来选择Outer的超类型成员,但它们仍然在类型为Outer的实例中访问。没有发生任何扩展转换。 - Holger
1
@charlie,没有理由认为它的行为类似于扩展转换。因为如果你执行Object x = Outer.this,扩展转换仍然会发生。任何东西都可以被分配给Object而不需要强制转换。此外,这样的强制转换也没有任何效果,因为Java中的所有函数都是虚拟的。 - Sergei Tachenov
1
@charlie,我明白了。然而,“String s = Outer.super.toString()”和“Object x = Outer.super; String s = x.toString();”在Eclipse中也不是同一件事情,对吧?这是将其视为错误的另一个原因。 - Sergei Tachenov
3
@charlie,这个特定的例子中也许两者是相同的,但我是在谈论一般性的含义。假设 Outer 重写了 toString() 方法。那么 System.out.println(Outer.super.toString()) 会忽略重写并打印类似于 package.Outer@12345 的东西,而 Object x = Outer.super; System.out.println(x.toString()); 则可能会打印重写方法返回的任何内容。 - Sergei Tachenov
3
@Sergey Tachenov: 正确的。构造函数 Outer.super.toString() 允许忽略包含在 Outer 类中的 toString() 实现。这就是为什么合成访问器方法已在 Outer 类中生成,因为只有声明类本身才能忽略重写的方法,因此内部类需要这种帮助来实现。相反,变量 x 是否具有类型 OuterObject 都无所谓,调用 x.toString() 总是会调用最具体的方法,即 Outer 中的方法(如果存在的话)。 - Holger
显示剩余9条评论

-1

这符合JLS的规定。JLS规范中指出:“super.Identifier形式是指当前对象的名为Identifier的字段,但将当前对象视为当前类的超类的实例。”

基于此,super是超类的实例。

JLS还指出:“如果当前类不是T类的内部类或T本身,则在编译时会出现错误。”

在您的情况下,这显然是一个内部类。您能否找出Eclipse中使用的Java版本。


2
问题不是关于super.Identifier,而是关于没有标识符的形式,这是不正确的。 - Sergei Tachenov
1
不行。因为super.IdentifierPrimary.Identifier在语法上是不同的结构。后者实际上会被求值,你可以访问该值的成员。前者则是一个特殊的结构,在其中,super实际上指的是this,只是成员选择算法不同而已。 - Sergei Tachenov
1
@AshraffAliWahab,“super应该包含超类实例”并不完全正确。super并不包含超类实例,因为只有一个实例(即this)。super是一个关键字,它只是告诉编译器使用超类中的代码(取决于它所使用的上下文)。 - Pshemo
1
这意味着this对象被解释为实际上是超类的一个实例,而不考虑覆盖。但是,“当前对象”一词指的是this,而不是super关键字,它是一个关键字,而不是某种特殊变量。 - Sergei Tachenov
1
换句话说,它确实决定了当前对象的视图,但这并不意味着它会评估为当前对象。 - Sergei Tachenov
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接