23

如果我有这样一个我无法更改的基类:

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。如果我可以将StringorAnyObject作为我的返回类型 inside 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 应该做出决定:如果是相同的返回类型,为什么不允许覆盖;如果不是,为什么我不能添加此方法?根据常识,告诉我它是相同的,但不允许它被覆盖,这很奇怪。

4

8 回答 8

11

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> {
    T id (T x) {...}
}
class D extends C<String> {
    Object id(Object x) {...}
}

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

  • 这两个方法具有相同的名称,id
  • D 可以访问 C.id(String)
  • 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.id(Integer) 可供 D 访问
  • 这两种方法具有不同的签名(并且都不是另一个的子签名)
  • D.id(String) 覆盖 C.id(String) 和 D.id(Integer) 覆盖 I.id(Integer) 但两个被覆盖的方法具有相同的擦除

还给出了允许从超级到子的情况的示例

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

考虑这个例子:

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。相反,该代码将是非法的。这将显着抑制泛型的使用,因为库编写者会犹豫迁移现有代码。

于 2012-09-25T08:49:21.330 回答
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
    }

}

这显然是一个问题。

于 2012-09-25T09:51:46.763 回答
2

从 JLS 部分8.4.8.3 覆盖和隐藏

如果类型声明 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 第 8.4.2 节):

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

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

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

于 2012-09-25T09:57:00.123 回答
1

想象一下下面的调用。

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

实际上,您正在调用一个泛型方法,但您没有传入任何类型参数。就目前情况而言,这不是什么大问题。无论如何,这些类型参数在代码生成期间都会消失。然而,物化从未被取消,Java 的管理者已经尝试并继续尝试保持这扇门敞开。如果类型参数被具体化,您的调用将不会为需要一个的方法提供任何内容。

于 2012-09-25T08:57:59.247 回答
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;
    }
}

尽管我确信其他人的回答是善意的。尽管如此,一些答案似乎主要误解了 JLS

上面只更改类声明,导致编译成功。仅这一事实就意味着@RafaelT 的原始签名作为子签名确实非常好——与其他人的建议相反

我不会犯与其他回答者似乎犯的相同的错误,并试图假装我完全了解 JLS 泛型文档。我承认我还没有 100% 确定地弄清楚 OP 最初编译失败的根本原因。

但我怀疑这OP在 Java 泛型的典型令人困惑和古怪的微妙之处之上Object作为返回类型的不幸组合有关。

于 2016-10-22T04:03:24.113 回答
0

你应该阅读擦除

在您的示例中:

public class D extends A{

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

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

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

于 2012-09-25T09:01:16.593 回答
0

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

于 2012-09-25T09:59:02.713 回答
0

有一个概念问题。假设C#get()覆盖A#get()

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

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

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

于 2012-09-25T16:32:33.900 回答