【问题标题】:Java Generics, the type gets lostJava 泛型,类型丢失
【发布时间】:2019-05-26 16:12:37
【问题描述】:

以下示例虽然看似正确,但无法编译(Eclipse Neon 3、Java 1.8):

class Test {    

    public static class SomeForm<IF extends SomeForm<IF>> {

    }

    public static class BaseFF<IF extends SomeForm<IF>> {

    }

    public static class AuxFF<IF extends SomeForm<IF>>
            extends BaseFF<IF> {

    }

    public interface Interface<IF extends SomeForm<IF>, FF extends BaseFF<IF>> {
        FF getFF1();        
    }

    public static class ZBaseUnit<IF extends SomeForm<IF>, FF extends BaseFF<IF>>
            implements Interface<IF, FF> {

        @Override
        public FF getFF1() {
            return null;
        }

    }

    public static class ZMyUnit<IF extends SomeForm<IF>, FF extends AuxFF<IF>>
            extends ZBaseUnit<IF, FF> {

    }

    public static class ZMyCheck<IF extends SomeForm<IF>, U extends ZMyUnit<IF, ?>> {
        U unit;

        void f() {
            BaseFF<IF> ff1 = unit.getFF1();
        }

    }

}

Eclipse 说(在 f() 方法内的一行):

“类型不匹配:无法从 capture#2-of ? 转换为 Test.BaseFF”。

但是,如果我从接口 Interface(以及 ZBaseUnit 类中的 @Override 注释)中删除方法 getFF1,它会编译。这背后有什么逻辑吗?直观上看,传给Interface的FF和传给ZBaseUnit的FF是一样的,所以应该没有区别……

另外,如果我在 ZMyUnit 中添加方法没有错误:

void f() {
    BaseFF<IF> ff1 = getFF1();
}

任何帮助将不胜感激!

【问题讨论】:

  • 更有趣的是:我不会假设ZMyUnit&lt;IF, ? &gt; unit; 而不是U unit; 来编译,因为在这两种情况下你指的是同一个基类。但它可以编译。

标签: java eclipse generics compiler-errors


【解决方案1】:

关于ZMyCheck 类和...中的变量初始化

    public static class ZMyCheck<IF extends SomeForm<IF>, U extends ZMyUnit<IF, ?>> {
        U unit;

        void f() {
            BaseFF<IF> ff1 = unit.getFF1();
        }

    }

...正确性最终归结为表达式unit.getFF1() 的类型,以及该类型是否是BaseFF&lt;IF&gt; 的子类型。该表达式的类型是实例变量unit 类型的函数,作为类型擦除的结果,它被解释为类型变量U 的上限,即ZMyUnit&lt;IF, ?&gt;

关于unit.getFF1()的类型,那么JLS指定

[一个类型的]字段、方法和构造函数的类型是[那个类型]的捕获转换中的字段、方法和构造函数的类型。

(JLS 10, 4.5.2)

因此,我们需要确定ZMyUnit&lt;IF, ?&gt; 类型的捕获转换,如section 5.1.10 中指定的那样。规范在这里变得相当技术性,但底线是ZMyUnit 的第二个类型参数的上限考虑在内,因此存在到类型ZMyUnit&lt;IF, AuxFF&lt;IF&gt;&gt; 的捕获转换。根据该类型解释,方法getFF1() 返回类型AuxFF&lt;IF&gt;,这需要扩大引用转换为类型BaseFF&lt;IF&gt;,这就是我们所需要的。

因此,正如@JohnMcClane 也回答的那样,Eclipse 的编译器拒绝提供的代码是不正确的。为通配符添加适当的上限似乎可以解决该问题,而不会更改代码相对于 JLS 的语义。

【讨论】:

    【解决方案2】:

    如果您将此验证码添加到您的班级

    public static class GF extends SomeForm<GF> {
    }
    
    public static void main(String[] args) {
        ZMyCheck<GF, ZMyUnit<GF, AuxFF<GF>>> z = new ZMyCheck<>();
        z.unit = new ZMyUnit<>();
        z.f();
        System.out.println("OK");
    }
    

    并使用标准的 JDK 命令行工具(javacjava),代码将编译并运行成功。如果您使用 NetBeans 也是如此(我无意以任何方式推广 NetBeans)。

    因此,问题是特定于 Eclipse 的。 Eclipse 有自己的内置编译器,您似乎发现了它的一个弱点(也许您需要提交错误报告)。无法确定通配符? 实际上是指扩展AuxFF&lt;IF&gt; 的类型(因此,BaseFF&lt;IF&gt;)。您需要明确指出:

    public static class ZMyCheck<IF extends SomeForm<IF>,
        U extends ZMyUnit<IF, ? extends AuxFF<IF>>> {...}
    

    然后一切都会顺利编译和运行。

    【讨论】:

    • 这很好地确定了问题的性质,但它有点解释为什么 JDK 的编译器是正确的,而不是 Eclipse 的。
    • @JohnBollinger JDK 的编译器是对的,因为它是 Java 编译器的参考实现。此外,OP 开始提出问题,说代码 似乎正确(我同意这一点)后来添加了一些为什么这个特定错误看起来很奇怪的原因。显然,如果ZMyUnit 对第二个参数有类型界限,那么这个界限也应该施加在ZMyCheck 类的U 参数界限的通配符上。
    • 不,@JohnMcClane。任何 Java 编译器的正确性都取决于它是否符合 Java 语言规范和 Java VM 规范。这包括 JDK 的编译器,多年来它确实存在一些错误。该编译器完全有可能成为错误的编译器。至于您的“显然,...”,这当然是一个似是而非的论点,但除非该论点与 JLS 一致,否则它的合理性最终是无关紧要的。我并不是说它不一致一致,但这种分析并非微不足道。
    • 谢谢@JohnMcClane!如果我明确指定“?”的类型,我认为不会有任何改变。通配符,但现在我发现我错了。也感谢您从 java/javac 的角度验证我的代码是否正确。
    猜你喜欢
    • 1970-01-01
    • 2021-09-28
    • 2013-11-17
    • 1970-01-01
    • 1970-01-01
    • 2021-07-13
    • 1970-01-01
    • 2020-02-05
    • 2014-04-17
    相关资源
    最近更新 更多