一般用Java覆盖一个方法

问题描述 投票:22回答:8

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

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的请求返回类型。如果我可以把StringAnyObject作为我的返回类型在B内,为什么我不被允许将<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应该做出决定:如果它是相同的返回类型,为什么不允许覆盖;如果不是,为什么我不能添加这种方法?告诉我它是相同的,但不允许它被覆盖,非常奇怪,基于常识。

java generics casting type-conversion method-overriding
8个回答
10
投票

JLS # 8.4.2. Method Signature

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

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

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

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
  • D.id(String)可供D访问
  • D.id(Object)的签名不是C.id(String)的签名
  • 这两种方法具有相同的擦除

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

 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.访问D.id(整数)
  • 这两种方法有不同的签名(也不是另一种的子签名)
  • D.id(String)覆盖C.id(String)和D.id(Integer)覆盖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。相反,代码是非法的。这将极大地抑制泛型的使用,因为库编写者会犹豫是否迁移现有代码。


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
投票

来自JLS部分8.4.8.3 Overriding and hiding

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

  1. m1和m2具有相同的名称。
  2. m2可从T访问。
  3. m1的签名不是m2签名的子签名(§8.4.2)。
  4. m1的签名或某些方法m1覆盖(直接或间接)具有与m2的签名相同的擦除或某种方法m2覆盖(直接或间接)。

1和2成立。

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

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

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

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


1
投票

想象一下以下的调用。

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

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


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的泛型的典型混乱和古怪的微妙之处有关。


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警告的原因。


0
投票

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


0
投票

这是一个概念上的问题。假设C#get()凌驾于A#get()

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

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

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

© www.soinside.com 2019 - 2024. All rights reserved.