一般用Java覆盖一个方法

如果我有这样的基类,我无法改变:

public abstract class A { public abstract Object get(int i); } 

我尝试用这样的B类扩展它:

 public class B extends A{ @Override public String get(int i){ //impl return "SomeString"; } } 

一切都好。 但是如果我尝试的话,我试图让它更通用的失败:

 public class C extends A{ @Override public  T get(int i){ //impl return (T)someObj; } } 

我想不出有什么理由不应该被禁止。 根据我的理解,generics类型T绑定到一个Object – 它是A的请求返回类型。 如果我可以将StringAnyObject作为我的返回类型放在B ,为什么我不允许在我的C类中放入 T

从我的观点来看,另一种奇怪的行为是这样的另一种方法:

 public class D extends A{ @Override public Object get(int i){ //impl } public  T get(int i){ //impl } } 

也是不允许的,提供了DuplicateMethod的提示。 这个,至少让我困惑,我认为Java应该做出决定:如果它是相同的返回类型,为什么不允许覆盖; 如果不是,为什么我不能添加这个方法? 告诉我它是相同的,但不允许它被覆盖,非常奇怪,基于常识。

JLS#8.4.2。 方法签名

方法m1的签名是方法m2的签名的子签名,如果:

  • m2与m1具有相同的签名,或

  • m1的签名与m2签名的擦除(§4.6)相同。

按照上述规则,因为您的父母没有擦除而您的孩子有一个擦除,所以它不是有效的覆盖。

JLS#8.4.8.3。 覆盖和隐藏的要求

例8.4.8.3-4。 擦除会影响覆盖

一个类不能有两个具有相同名称和类型擦除的成员方法:

 class C { T id (T x) {...} } class D extends C { Object id(Object x) {...} } 

这是非法的,因为D.id(Object)是D的成员,C.id(String)以D的超类型声明,并且:

  • 这两种方法具有相同的名称,id
  • D.id(String)可供D访问
  • D.id(Object)的签名不是C.id(String)的签名
  • 这两种方法具有相同的擦除function

类的两种不同方法可能不会覆盖具有相同擦除的方法:

  class C { T id(T x) {...} } interface I { T id(T x); } class D extends C implements I { public String id(String x) {...} public Integer id(Integer x) {...} } 

这也是非法的,因为D.id(String)是D的成员,D.id(Integer)在D中声明,并且:

  • 这两种方法具有相同的名称,id
  • D可以访问D.id(整数)
  • 这两种方法有不同的签名(也不是另一种的子签名)
  • D.id(String)覆盖C.id(String)和D.id(Integer)覆盖I.id(整数)但两个重写方法具有相同的擦除

另外,它给出了允许从超级到子级的情况的示例

子签名的概念旨在表达两种方法之间的关系,这两种方法的签名不相同,但其中一种方法可以覆盖另一种方法。 具体来说,它允许一种方法,其签名不使用generics类型来覆盖该方法的任何泛化版本。 这很重要,因此库设计者可以独立于定义库的子类或子接口的客户端自由地生成方法。

考虑这个例子:

 class CollectionConverter { List toList(Collection c) {...} } class Overrider extends CollectionConverter { List toList(Collection c) {...} 

}

现在,假设这个代码是在引入generics之前编写的,现在CollectionConverter类的作者决定生成代码,因此:

  class CollectionConverter {  List toList(Collection c) {...} } 

如果没有特殊的分配,Overrider.toList将不再覆盖CollectionConverter.toList。 相反,代码是非法的。 这将极大地抑制generics的使用,因为库编写者会犹豫是否迁移现有代码。

好吧,对于第一部分,答案是Java不允许generics方法覆盖非generics方法,即使擦除是相同的。 这意味着即使你只有覆盖方法,它也不会起作用:

  public  Object get(int i) 

我不知道为什么Java会出现这种限制(给它一些思考),我只是认为它与为子类化generics类型实现的特殊情况有关。

你的第二个定义基本上会转化为:

 public class D extends A{ @Override public Object get(int i){ //impl } public Object get(int i){ //impl } } 

这显然是个问题。

从JLS部分8.4.8.3覆盖和隐藏 :

如果类型声明T具有成员方法m1并且存在以Tforms声明的方法m2或T的超类型以使得满足以下所有条件 ,则为编译时错误:

  1. m1和m2具有相同的名称。

  2. m2可从T访问。

  3. m1的签名不是m2签名的子签名(第8.4.2节)。

  4. m1的签名或某些方法m1覆盖(直接或间接)具有与m2的签名相同的擦除或某种方法m2覆盖(直接或间接)。

1和2成立。

3也是如此(引自JLS第8.4.2节 ):

子签名的概念旨在表达两种方法之间的关系,这两种方法的签名不相同,但其中一种方法可以覆盖另一种方法。 具体来说,它允许一种方法,其签名不使用generics类型来覆盖该方法的任何泛化版本。

而你正在采用另一种方式:一种generics类型的方法覆盖一个没有generics的方法。

因为擦除的签名是相同的,所以4也成立: public Object get(int i)

想象一下以下的调用。

 A a = new C(); a.get(0); 

实际上,您正在调用generics方法,但您没有传入任何类型的参数。 事实上,这不是一个问题。 无论如何,这些类型参数在代码生成期间消失。 然而,具体化从未被取消,而语言的Java管家已经尝试并继续尝试保持这扇门的开放。 如果已声明类型参数,则您的调用不会向需要其中的方法提供任何参数。

@RafaelT的原始代码导致此编译错误…


 C.java:3: error: C is not abstract and does not override abstract method get(int) in A public class C extends A { ^ C.java:5: error: name clash: get(int) in C and get(int) in A have the same erasure, yet neither overrides the other public  T get ( int i ){ ^ where T is a type-variable: T extends Object declared in method get(int) 

让@ RafaelT的C子类成功编译的最简单,最直接的解决方案是……


 public abstract class A { public abstract Object get(int i); } 

 public class C extends A { @Override public T get(int i){ //impl return (T)someObj; } } 

虽然我相信其他人对他们的答案意味着很好。 然而,一些答案似乎已经严重误解了JLS

上面只改变了类声明,导致编译成功。 仅这一事实意味着@RafaelT的原始签名确实完全可以作为子签名与其他人的建议相反

我不会犯同样的错误,其他人回答似乎已经做了,并试图假装我完全了解JLSgenerics文档。 我承认我没有100%确定,OP的原始编译失败的根本原因。

但我怀疑它与OP的使用Object作为返回类型的不幸组合有关,而这种返回类型基于Javagenerics的典型混乱和古怪的微妙之处。

你应该阅读擦除

在你的例子中:

 public class D extends A{ @Override public Object get(int i){ //impl } public  T get(int i){ //impl } } 

编译器生成的generics方法的字节代码与非generics方法相同; 也就是说, T将被替换为上限,即Object 。 这就是您在IDE中收到DuplicateMethod警告的原因。

将方法声明为 T get(int i)没有意义而不在其他地方声明T – 在方法的参数中或在封闭类的字段中。 在后一种情况下,只需使用类型T对整个类进行参数化。

这是一个概念性问题。 假设C#get()重写A#get()

 A a = new C(); Object obj = a.get(); // actually calling C#get() 

但是C#get()需要一个T – 应该是什么? 没有办法确定。

您可以抗议由于擦除而不需要T 今天这是正确的。 但是擦除被认为是一种“临时”的解决方法。 类型系统在很大程度上不假设擦除; 它实际上是经过精心设计的,因此可以在未来版本的Java中完全“可恢复”,即无需擦除,而不会破坏现有代码。