【问题标题】:Overriding a generic method by a generic one in Java用Java中的泛型方法覆盖泛型方法
【发布时间】:2015-03-09 02:38:42
【问题描述】:

Angelica Langer 在她关于泛型的常见问题解答中说(请参阅Technicalities.FAQ822):

如果方法具有不同边界的类型参数,那么它们 不要覆盖,因为这些方法的签名不是 覆盖等效。请记住,类型参数边界是 泛型方法的签名。

示例(泛型子类型方法重载泛型超类型 方法;不推荐):

class Super {
   public <T> void set( T arg) { ... }
   public <T> T get() { ... }
}
class Sub extends Super {
   public <S extends Number > void set( S arg) { ... } // overloads
   public <S extends Number > S get() { ... }         // overloads
}

我不明白为什么get 方法在Sub 类中被重载。据我所知,这应该是编译时错误,因为getSubSuper 中具有相同的签名(返回类型不是其中的一部分)。

更让我困惑的是,我用来测试代码的 IDE (IntelliJ IDEA 14.0.3) 将Sub 中的get 突出显示为编译错误,并显示下一条消息:

'Sub' 中的 'get()' 与 'Super' 中的 'get()' 冲突;两种方法都有相同的擦除,但都不会覆盖另一个。

但是当我运行程序时,它编译并执行没有问题。我想 IntelliJ 在分析代码时存在某种错误,正确的是 Angelica 在她的常见问题解答中所说的。但我无法抓住重点。

【问题讨论】:

  • 在 Eclipse 中,您可以将内置编译器设置为更严格,并将某些条件视为错误(例如赋值给 self 或通过实例访问静态方法)。我想在 IDEA 中也可以这样做。
  • 问题出在被覆盖的方法的类型参数与超类方法的类型参数的界限不同。
  • 正如您引用的帖子中所说,类型参数边界是泛型方法签名的一部分。所以&lt;T&gt; T get()&lt;S extends Number &gt; S get() 有不同的签名。名字冲突仍然很糟糕,所以海报告诉它是不推荐
  • Kocko 的回答基本上得出结论,签名冲突并因此出现错误,但这不应该使它成为有效的覆盖吗?而且,如果类型参数边界是签名的一部分,那么它们是不同的,因此它不应该是有效的重载吗?我想回答/评论是什么让编译器说,“它既不是重载,也不是覆盖,因此是错误。”
  • @biziclop 这是一个完全有效的覆盖。覆盖方法的返回类型可以是被覆盖方法的返回类型的协变体(子类型)。

标签: java generics


【解决方案1】:

根据JLS,方法签名不包括返回类型,而只包括方法名称及其参数的类型。这意味着在编译 Super 和 Sub 时,应该返回一个编译错误,因为 Sub.get() 与 Super.get() 具有相同的擦除,但既不覆盖也不重载 Super.get()。它不能重写,因为有界类型 X 扩展 Number 不是 X 类型的子类型,并且它不能重载,因为返回类型不是方法签名的一部分。在这种情况下,Sub.set 重载 Super.set。

至于为什么你可以编译和运行它。如果您正在运行 Java 6,那么 Java 6 中有一个已知的bug 可以编译 Super 和 Sub。在 Java 7 中,这是固定的,不允许这样做。

【讨论】:

    【解决方案2】:

    我认为public &lt;S extends Number&gt; S get() 的擦除与public &lt;T&gt; T get() 的擦除不同或协变。

    编译并运行:

    class B {
        public <T> T get() {
            return null;
        }
    
    }
    
    class A extends B {
        @Override
        public <S> S get() {
            return null;
        }
    

    这不是:

    class B {
            public <T> T get() {
                return null;
            }
    
        }
    
        class A extends B {
            @Override
            public <S extends Number> S get() {
                return null;
            }
    

    【讨论】:

    • 没错。第一个代码编译并运行,而第二个代码没有编译并运行,因为您在get 中使用了@Override 注释。我知道 Angelika Langer 的常见问题解答它被超载,而不是被覆盖。我不明白的是为什么
    • @Bleyder 因为ST 是输入参数。
    • @biziclop STboth 示例中的输入参数。但是,在第二个中,这是一个编译时错误,因为 B 没有覆盖方法 get... 它正在重载!为什么会这样?第二个示例中的B 如何重载而不覆盖?
    • 这正是因为ST是输入参数,当你重写一个方法时你不能缩小输入参数的类型。正如我已经用这个例子指出的那样:Super foo = new Sub(); String bar = foo.&lt;String&gt;get();。如果Sub.get()Super.get() 的覆盖,则将S 设置为输入String,这是不允许的。
    【解决方案3】:

    据我了解,为什么它是重载而不是覆盖是因为编译器不认为它是覆盖等效的。在 Angelika Langer 的常见问题解答中,您会看到它讨论了为什么不按预期覆盖方法。

    http://www.angelikalanger.com/GenericsFAQ/FAQSections/ProgrammingIdioms.html#FAQ051

    总而言之,相同的方法具有相同的名称和参数,但基于泛型的子类型被定义为不同的。即使使用 override 注释,编译器也将其视为重载方法。在使用泛型时,为方法增加了一层,泛型类型作为方法的一部分应用,可以唯一地定义它。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-04-04
      • 1970-01-01
      • 2014-05-23
      • 1970-01-01
      相关资源
      最近更新 更多