一般用Java覆盖一个方法

时间:2012-09-25 08:38:55

标签: java generics casting type-conversion method-overriding

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

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 extends Object> T get(int i){
        //impl
        return (T)someObj;
    }
}

我想不出有什么理由不应该被禁止。根据我的理解,泛型类型T绑定到Object - 这是A的请求返回类型。如果我可以在String中添加AnyObjectB作为我的返回类型,为什么我不允许将<T extends Object> T放入C类中?

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

public class D extends A{

    @Override
    public Object get(int i){
        //impl
    }

    public <T extends Object> T get(int i){
        //impl
    }
}

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

8 个答案:

答案 0 :(得分:10)

JLS # 8.4.2. Method Signature

  

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

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

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

  •   

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

JLS#8.4.8.3. Requirements in Overriding and Hiding

例8.4.8.3-4。擦除影响覆盖

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

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

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

  • 这两个方法具有相同的名称,id
  • C.id(String)可供D
  • 访问
  • D.id(对象)的签名不是其中的签名 C.id(字符串)
  • 这两种方法具有相同的擦除

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

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

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

  • 这两个方法具有相同的名称,id
  • D.id(整数)可供D
  • 使用
  • 这两种方法有不同的签名(也不是 另一个的子签名)
  • D.id(String)覆盖C.id(String)和D.id(整数) 覆盖I.id(整数)但两个被覆盖的方法具有相同的 擦除

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

  

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

考虑一下这个例子:

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

}

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

 class CollectionConverter {
   <T> List<T> toList(Collection<T> c) {...}
 }

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

答案 1 :(得分:5)

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

 public <T extends Object> Object get(int i)

我不知道为什么Java会出现这种限制(考虑一下),我只是认为它与为泛型类进行子类化而实现的特殊情况有关。

您的第二个定义基本上会转换为:

public class D extends A{

    @Override
    public Object get(int i){
    //impl
    }

    public Object get(int i){
        //impl
    }

}

这显然是一个问题。

答案 2 :(得分:2)

来自JLS部分8.4.8.3 Overriding and hiding

  

如果类型声明T具有成员方法m1并且存在以T声明的方法m2或T的超类型使得以下所有条件成立,那么这是编译时错误:

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

  2.   
  3. m2可从T。

  4. 访问   
  5. m1的签名不是m2签名的子签名(§8.4.2)。

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

  8.   

1和2持有。

3也有,因为(引自JLS section 8.4.2):

  

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

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

由于删除的签名相同,因此

4也成立:public Object get(int i)

答案 3 :(得分:1)

想象一下以下的调用。

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

实际上,您正在调用泛型方法,但是您没有传入任何类型的参数。事实上,这不是一个问题。无论如何,这些类型参数在代码生成期间消失。然而,从未取消过具体化,并且语言已经尝试过Java的管理员continue to try并保持开放。如果定义了类型参数,则您的调用不会向需要一个的方法提供任何参数。

答案 4 :(得分:1)

@ 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: <T>get(int) in C and get(int) in A have the same erasure, yet neither overrides the other
    public  <T extends Object> T  get ( int i ){ 
                              ^
  where T is a type-variable:
    T extends Object declared in method <T>get(int)

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

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

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

以上只对类声明的更改导致编译成功。仅这一事实意味着@RafaelT的原始签名确实完全正常as subsignatures - 与其他人的建议相反

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

但是我怀疑它有 与OP使用Object作为the return type的不幸组合。 Java杂志中典型的令人困惑和古怪的微妙之处。

答案 5 :(得分:0)

您应该阅读erasure

在你的例子中:

public class D extends A{

  @Override
  public Object get(int i){
      //impl
  }

  public <T extends Object> T get(int i){
      //impl
  }
}

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

答案 6 :(得分:0)

如果没有在其他地方声明<T extends Object> T get(int i) - 在方法的参数或封闭类的字段中,将方法声明为T是没有意义的。在后一种情况下,只需使用类型T参数化整个类。

答案 7 :(得分:0)

存在一个概念性问题。假设C#get()会覆盖A#get()

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

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

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