Java泛型和覆盖方法

9
为什么会出现以下情况:
public class one{
   public <T extends Foo> Bar<Foo> function1() {}
   public Bar<Foo> function2(){}
}

public class two<F extends Foo> extends one{
   public Bar<F> function1(){} //Doesn't throw an error
   public Bar<F> function2(){} //Throws an error
}

<T extends Foo>,我是不是在说Foo可以被超类型覆盖?
注意:我的问题不是为什么function2()会抛出错误...而是为什么function1()不会抛出错误。

2
这段程序相关的内容应翻译成:public <T extends Foo> Bar<Foo> function1() {} 这段代码能编译吗?我不知道那个返回类型是什么。 - Cruncher
1
在我看来,这里的返回类型类似于 <T extends Foo> Bar<Foo> - Cruncher
@elmes 我认为他对此很感兴趣,并想知道它为什么有效(对于函数1)。 - Cruncher
@MrTi 你可能可以将 one 设计成泛型并继承 one<T> 而不只是 one。像 奇妙的递归模板模式 这样的东西。 - emesx
我无法理解它。在我看来(特别是因为 @Override 没有报错),Class two 完全不依赖于 Class one。我没有看到任何函数定义中的错误。 - scottb
显示剩余21条评论
3个回答

6
这可能是编译器的一个错误。 Two.function1 被认为是 One.function1 的一个覆写方法的原因在于 http://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.4.2 中提到
Two.function1 的签名与 One.function1 的擦除签名相同。
这是为了允许遗留子类型(Two)在超类型(One)泛型化后仍然可以编译。
之后,javac 需要检查返回类型:

http://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.4.8.3

如果一个带有返回类型 R1 的方法声明 d1 覆盖或隐藏了另一个带有返回类型 R2 的方法声明 d2,则 d1 必须是可替代返回类型的(§8.4.5),否则会发生编译时错误。

http://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.4.5

R1 是 R2 的子类型,或者 R1 可以通过未经检查的转换(§5.1.9)转换为 R2 的子类型,或者 R1 = |R2|。

http://docs.oracle.com/javase/specs/jls/se7/html/jls-5.html#jls-5.1.9

存在从原始类或接口类型(§4.8) G 到任何形式为 G 的参数化类型的未经检查的转换。
在 R1=Bar<F>, R2=Bar<Foo> 的情况下,不适用未经检查的转换。这就是为什么 Javac 报告 function2 返回类型冲突的原因。
Javac 应该对 function1 报告相同的错误。我猜测在之前的步骤中,javac 取了 One.function1 的擦除版本,即 Bar function1(),并错误地使用该版本来检查 Two.function1 - 在这里,R2=Bar,因此 R1R2 的子类型($4.10.2),因此返回类型是兼容的。(但如果这个理论是正确的,就不应该有 "unchecked" 警告)

3

方法function2不是通用的。它返回一个Bar<Foo>

子类可以在覆盖的方法中返回返回类型的子类。这被称为协变返回类型

但是,即使FFoo的子类,Bar<F>也不是Bar<Foo>的子类 - 在Java中,泛型不是协变的(数组是协变的)。


我的IDE实际上也警告我function1未经检查的重写:返回类型需要未经检查的转换。我认为这只是因为基类中存在一个通用类型和类型擦除的存在而产生的警告。我会说你在这里操作的是非通用的Bar


请现在解释一下 function1() - Sotirios Delimanolis
在我看来,第二个类不能扩展第一个类,而第二个类的含义仍然相同。对我来说,这是一个非常奇特的错误。 - scottb
如果我只从第一类的角度来看,通用类型什么也没做。 - Nathan Merrill
似乎我不理解擦除。 <T extends Foo> 对擦除会有什么影响? - Nathan Merrill
1
在方法上定义类型参数会将其标记为泛型方法。这将改变它的处理方式。 - oconnor0
显示剩余2条评论

2

<T extends Foo>定义了一个类型T,必须是Foo的某个子类型。实际上,在onetwo中都没有使用过。

我猜这是因为编译器正在处理作用域为one.function1()的泛型类型定义时,擦除函数类型的方式不同。

查看JLS 8.4.8.3定义的有效方法覆盖。在其中有一个关于“未经检查的转换”警告的示例。在那里,它链接到8.4.5描述了什么是“返回类型可替代”的概念,然后再链接到5.1.9,说明了未经检查的转换。

我没有答案,但似乎将该方法标记为通用方法(而不仅仅是使用参数化类型的方法)会触发允许进行未经检查的转换 - 不管是故意还是因为错误引起的。

编辑:鉴于此

public class Main {
  class Bar<X> {}
  class Foo{}
  class one{
   public <T extends Foo> Bar<Foo> function1() { return null; }
   public Bar<Foo> function2(){ return null; }
  }
  class two<F extends Foo> extends one{
   public Bar<F> function1(){ return null; } //Doesn't throw an error
   public Bar<F> function2(){ return null; } //Throws an error
  }
}

使用 javac -Xlint:unchecked Main.java 进行编译:
Main.java:9: warning: [unchecked] function1() in Main.two overrides <T>function1() in Main.one
   public Bar<F> function1(){ return null; } //Doesn't throw an error
                 ^
  return type requires unchecked conversion from Main.Bar<F> to Main.Bar<Main.Foo>
  where F,T are type-variables:
    F extends Main.Foo declared in class Main.two
    T extends Main.Foo declared in method <T>function1()
Main.java:10: error: function2() in Main.two cannot override function2() in Main.one
   public Bar<F> function2(){ return null; } //Throws an error
                 ^
  return type Main.Bar<F> is not compatible with Main.Bar<Main.Foo>
  where F is a type-variable:
    F extends Main.Foo declared in class Main.two
1 error
1 warning

至少你在function1()上得到了一个警告,这比我得到的要多。这让我想知道<T extends Foo>是否告诉编译器进行某种转换。 - Nathan Merrill
你使用的编译器是哪个? - oconnor0
我在Sun/Oracle JDK和Eclipse JDK处理通用类型方面遇到了问题。 - oconnor0
jdk1.7.0_17在eclipse上。你遇到了哪些问题? - Nathan Merrill
一个编译器可以编译带有泛型的代码而不会出现问题,而另一个则会因为错误而退出。我建议从命令行编译此示例,以查看是否可以通过这种方式获得警告。 - oconnor0

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