【问题标题】:Compilation error while overriding abstract enum method with generic return type使用泛型返回类型覆盖抽象枚举方法时出现编译错误
【发布时间】:2017-03-05 06:46:13
【问题描述】:

这是一个正确编译的代码

public enum SupportedConversions {
    INTEGER {
        @Override
        public Integer convert( String ion, Object[]  aa) {
            return null;
        }
    },
    LONG {
        @Override
        public Long convert(final String ion, Object[] aa) {
            return null;
        }
    };

    public abstract <T> T convert(String val, Object[] aa);
}

但是当我将抽象函数参数更改为对象列表而不是数组时,我收到编译错误消息“方法不会从超类覆盖”。只有当返回类型是泛型时才会发生这种情况

错误代码示例

public enum SupportedConversions {
    INTEGER {
        @Override
        public Integer convert( String ion, List<Object>  aa) {
            return null;
        }
    },
    LONG {
        @Override
        public Long convert(final String ion, List<Object> aa) {
            return null;
        }
    };

    public abstract <T> T convert(String val, List<Object> aa);
}

这是否有原因不起作用。看起来更像是 Java 中的一个错误

【问题讨论】:

  • 这很奇怪,但我确信这是完全合乎逻辑的原因。如果从List 中删除类型参数,编译器错误就会消失。或者您可以将类型参数设置为T。带有数组的版本发出以下警告:“未经检查的覆盖:返回类型需要未经检查的转换。找到'java.lang.Long',需要'T'”
  • 是的,如果我在覆盖方法中删除显式类型的列表,编译错误就会消失。

标签: java generics enums java-8


【解决方案1】:

问题应该是“为什么可以编译第一个”而不是“为什么第二个会失败”。
两个都坏了。

类似的方法签名

<T> T convert(String val, Object[] aa)

说“无论调用者用什么替换T,这个方法都会返回一个兼容的结果”。这不是很有用,因为唯一有效的返回值是null,但至少,当您尝试在以这种方式声明的方法中返回不兼容的结果时,编译器会告诉您。

但是子类会像这样覆盖这个方法

Long convert(final String ion, Object[] aa)

换句话说,使用始终返回Long 的方法覆盖一个承诺返回调用者希望返回的任何内容的方法。这首先应该感觉不对...当您返回 null 时结果仍然兼容,但当您返回非 null Long 值时结果仍然兼容,编译器甚至不会警告您,因为Long 值与声明的返回类型 Long 兼容。

但是,编译器应该已经发出关于方法声明本身的警告。为了演示问题,使用该声明,您可以编写

String s = SupportedConversions.LONG.convert("bla", null);

编译器不会反对。如前所述,基本类型声明&lt;T&gt; T convert(…) 承诺返回调用者对T 的假设,而这里T 已被推断为String。当实现返回 Long 实例时,这显然会在运行时中断。


之所以可以编译,是因为它与 pre-Generics 代码的兼容性。其目的是允许具有不同“泛化”状态的库进行交互。例如。您可以使用最近的 jdk 编译 Java 1.4 应用程序代码,即使某些类覆盖了 now-Generic 方法。

因此,您的子类中不使用泛型的convert 方法可以覆盖基类的convert 方法。相比之下,像

这样的方法声明
Long convert(final String ion, List<Object> aa)

使用泛型,因此,不允许绕过泛型类型系统。如果您改用原始类型 List,您将再次获得一个可以绕过泛型的非泛型声明。


如果您现在想说,在这里假设前泛型行为是不合逻辑的,那么您并不孤单。不仅因为覆盖方法与被覆盖的通用声明位于同一编译单元(enum 声明)内,而且两者都在 enum 声明内,这是一种在 Java 5 之前不存在的语法结构(其中泛型介绍)。

此外,覆盖方法利用协变返回类型Long resp。 Integer,其中方法声明的擦除返回类型为 Object,它也不能出现在 Java 5 之前的代码中。

但这些(仍然)是规则。您应该注意此处的编译器警告。如果您没有收到警告(我知道 Netbeans IDE 的默认设置很草率),您应该尝试启用它们。


此代码无法修复。 enums 无法实现您想要做的事情。您可以删除类型参数T 并让基类型的方法声明返回Object,但enum 常量中的协变返回类型无关紧要,因为它们不是public API 的一部分。最好的选择是:

public interface SupportedConversions<T> {
    SupportedConversions<Integer> INTEGER = (String ion, Object[] aa) -> {
        return null;
    };
    SupportedConversions<Long> LONG = (String ion, Object[] aa) -> {
        return null;
    };
    public abstract T convert(String val, Object[] aa);
}

分别

public interface SupportedConversions<T> {
    SupportedConversions<Integer> INTEGER = (ion, aa) -> {
        return null;
    };
    SupportedConversions<Long> LONG = (ion, aa) -> {
        return null;
    };
    public abstract T convert(String val, List<Object> aa);
    // we can support both variants
    public default T convert(String val, Object[] aa) {
        return convert(val, Arrays.asList(aa));
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-31
    • 1970-01-01
    相关资源
    最近更新 更多