Java泛型 - 使用泛型函数扩展泛型类

8
我有一个简单的程序,如下所示:
package test;

public class TestGenericsInheritance {

    public static void main(String[] args) {}

    public static abstract class A<Q>{

        public void foo2(Q obj){}
        public abstract <T> void foo(T obj);
    }

    public static class C extends A<Object>{

        @Override
        public <T> void foo(T obj) {}
    }

    public static class B extends A{

        @Override
        public <T> void foo(T obj) {}
    }
}

正如您所看到的,它什么也没做。在Java 1.6和1.7上编译此程序文件会出现以下错误:

/D:/Projects/.../test/TestGenericsInheritance.java:[24,19] test.TestGenericsInheritance.B不是抽象的,也没有覆盖 test.TestGenericsInheritance.A中的抽象方法foo(java.lang.Object) /D:/Projects/.../test/TestGenericsInheritance.java:[27,25] 名称冲突: test.TestGenericsInheritance.B中的foo(T)和test.TestGenericsInheritance.A中的foo(T)具有相同的擦除,但都没有覆盖另一个 /D:/Projects/.../test/TestGenericsInheritance.java:[26,9] 方法未覆盖或实现超类型的方法

类C和B在语义上是相同的,但是类B不认为方法foo是A#foo的实现。为了使这段代码合规,我必须在类B中实现A#foo方法,并使用以下签名:

public void foo(Object obj) 

我的问题是,为什么我的程序无法编译?泛型类型Q和T是完全独立的,那么为什么我只有在为继承类A明确指定泛型类型Q时,才能实现泛型函数A#foo呢?
谢谢

我能够重现。更重要的是,同时拥有非泛型方法和泛型方法会因为类型擦除相同而产生错误。 - 4castle
Eclipse建议在B类中添加:@Override public void foo(Object obj) {},这确实可以使它编译通过。(1.8) - Jorn Vernee
@4castle你的修改使得错误信息变得混乱,因为它们提到了一个你已经删除的源文件名。 - Klitos Kyriacou
@KlitosKyriacou 不是我删除的,而是之前的编辑。我会回滚。 - 4castle
@4castle 很抱歉怪罪你,实际上是在你之前的那个人。这是一个有趣的问题,正如下面的答案所示,教训是尽可能避免使用原始类型。 - Klitos Kyriacou
1个回答

6

B扩展了原始类型A。因为您使用了原始类型,所有泛型信息都被擦除了,不仅是您没有指定的类型变量(请参见Java语言规范第4.6节)。这意味着A具有方法void foo(Object obj),而A<SomeType>具有方法<T> void foo(T obj)

如果您要覆盖一个方法,则必须具有相同的签名(在覆盖方法的类型擦除后可选) - 有趣的是,覆盖方法可以具有不同但更具体的返回类型。您两个方法的签名不同(您需要在覆盖方法中进行类型擦除),因此您的示例无法编译。

实施类型擦除的原因是向后兼容性。想法是新代码只会使用泛型,而只有旧代码会使用原始类型。因此,目标不是使原始类型最方便(因为新代码不应使用它们),而是使原始类型最兼容。

例如考虑类ArrayList,它是在Java 2中引入的(远在泛型之前)。它有一个方法public Object[] toArray(Object[] a)。在Java 5中引入了泛型,该方法的签名可以更改为public <T> T[] toArray(T[] a)(其中T不是元素类型)而不会有困难:由于实施类型擦除的方式,对于旧代码使用(或子类化)ArrayList而不是ArrayList<SomeType>,方法签名保持不变。


我还是不明白。在规范中哪里提到了“所有通用信息都被清除”? - 4castle
1
它说:“类型擦除还将构造函数或方法的签名(§8.4.2)映射到一个没有参数化类型或类型变量的签名。” 没有提到任何其他条件。 - Hoopje
@Hoopje,感谢您的解释。我明白了我的问题背后的原因。然而,在这种情况下,我看不出删除所有通用类型的意义。我理解类型参数应该被擦除。但是,如果方法参数与类型参数无关,为什么要擦除它?您能否提供任何需要进行此类擦除的情况? - MAREK
@MAREK 我更新了答案,也回答了那个问题。 - Hoopje

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