Java编译错误:用泛型参数覆盖泛型方法。

3
import java.util.Collection;

public abstract class A {

    public class R<T> { }

    public abstract <X> R<X> get1();

    public abstract <X> R<X> get2(Collection<String> p);

    public abstract <X> R<X> get3(Collection<Integer> p);

    public class B extends A {

        @Override
        public R<Object> get1() {
            return null;
        }

        @Override
        public R<Object> get2(Collection<String> p) {
            return null;
        }   

        @Override
        public <Object> R<Object> get3(Collection<Integer> p) {
            return null;
        }
    }
}
get1方法运行正常,但get2存在编译错误:
  • A.B类的get2(Collection)方法必须覆盖或实现一个超类型方法。

此错误仅在具有泛型参数时出现。 get3可以编译,但当然会有警告:
  • 类型参数Object隐藏了类型Object。

显然还有其他解决方法,但据我理解,这应该是一种合法的覆盖,我的问题更多的是为什么会出现这个编译错误。谢谢!

编辑:

抱歉,我的例子不够清晰。 因此,这里是一个新的例子来回应您的答案中的一些观点。

public abstract class A {

    public abstract class R<T> { }

    public abstract <X> R<X> get();

    public abstract <Y> R<Y> get2(Collection<String> p);

    public abstract <Z> R<Z> get3(Collection<Integer> p);

    public class B extends A {

        @Override
        public R<String> get() {
            return null;
        }

        @Override
        public R<Double> get2(Collection<String> p) {
            return null;
        }   

        @Override
        public <V> R<V> get3(Collection<Integer> p) {
            return null;
        }
    }
}
  • get2 的编译错误与上述相同,而对于 get,则会有一个明显的类型安全警告,我期望在 get2 上也会出现。
  • 我希望每个方法都有自己的类型,因此类类型不是解决方案。
  • 对于这个例子来说使用 Object 是个糟糕的决定,但这不是我的观点,旧的 get3 存在类型隐藏问题,如上所述。
  • 问题在于签名,就像 lexicore 所说。因此我将评论他的答案。

我稍后会检查你的更新并看看我能做什么。除此之外:这是一个理论问题还是你在尝试解决一个实际问题? - GhostCat
它源自一个真实的问题,但我已经有不同的可行解决方案。所以这更多是理论上的。 - Julian Sommerfeldt
1
通常这个问题更加复杂,而且不止一个类。我的意图只是提供一个小的可重现的例子。但是感谢您的建议。 - Julian Sommerfeldt
如果你将 Collection<String> 替换为 String,错误就会消失,因此似乎是由于参数类型是泛型引起的。可能是一个 bug,但 Oracle javac 和 ECJ 有相同的行为,所以更可能是由于 Java 语言规范中一些晦涩的泛型签名解析规则导致的。 - Sean Van Gorder
抱歉回复晚了。正如lexicore所说,您的答案并没有解释为什么“B.get2”没有覆盖“A.get2”。而且,这是我的真正问题,我认为它还没有得到解决,即使我仍然希望问题出在我的一方。;) - Julian Sommerfeldt
显示剩余4条评论
5个回答

5

在这里:

public <Object> R<Object> get3(Collection<Integer> p) {

这可能不是你的本意。

你在此引入了另一个称为Object类型变量

与此相比:

public R<Object> get2(Collection<String> p) {

换句话说:get2()使用的是java.lang.Object。get3()使用的是一个不幸被命名为Object泛型类型。你也可以这样写:
public <NONSENSE> R<NONSENSE> get3(Collection<Integer> p) {

最后,get2()也不是你要做的事情。请参考Michael的优秀回答。

正确,但这只是部分答案。修复它不会解决任何编译器警告。使用“Object”作为类型参数标识符将可以编译通过,尽管它有点违背了OP的意图。 - Michael
1
@Michael 正确;我为你的回答添加了“委托”! - GhostCat
我不明白为什么 B.get 覆盖了 A.get,但是 B.get2 却没有覆盖 A.get2B.get2A.get2 具有相同的签名。JLS:如果两个方法或构造函数 M 和 N 具有相同的名称、相同的类型参数(如果有)(§8.4.4),并且在将 N 的形式参数类型适应为 M 的类型参数之后,具有相同的形式参数类型,则它们具有相同的签名。 这些标准中哪些适用于 get,但不适用于 get2 - lexicore
@lexicore,你的“_B.get2A.get2具有相同的签名”评论是不正确的。我在我的回答中解释了原因。 - deduper

2
如GhostCat所提到的,get3中的Object实际上是一个类型变量。有趣的是,你本应该使用它,只是给它起了个让人困惑的名称。
要修复你的内部类,你必须重新声明所有三个方法的泛型类型参数:
public class B extends A
{
    @Override
    public <X> R<X> get1() {
        return null;
    }

    @Override
    public <X> R<X> get2(Collection<String> p) {
        return null;
    }

    @Override
    public <X> R<X> get3(Collection<Integer> p) {
        return null;
    }
}

然而,所有那些重复的<X>表明你应该将泛型类型参数移动到类A中。以下是我实现这个的方式:
abstract class A<X>
{
    public class R<T> { }

    public abstract R<X> get1();

    public abstract R<X> get2(Collection<String> p);

    public abstract R<X> get3(Collection<Integer> p);

    public class B extends A<X>
    {
        @Override
        public R<X> get1() {
            return null;
        }

        @Override
        public R<X> get2(Collection<String> p) {
            return null;
        }

        @Override
        public R<X> get3(Collection<Integer> p) {
            return null;
        }
    }
}

如果B适用于所有Object(从您的示例中不清楚),则可以使用以下代码替换上面的内部类:
public class B extends A<Object>
{
    @Override
    public R<Object> get1() {
        return null;
    }

    @Override
    public R<Object> get2(Collection<String> p) {
        return null;
    }

    @Override
    public R<Object> get3(Collection<Integer> p) {
        return null;
    }
}

1
这并不解释为什么 B.get2 没有覆盖 A.get2 - lexicore

2
"B.get2()没有覆盖A.get2()的原因是它们的类型参数不同,根据JLS的说法{{link1:...}}。"
"...如果两种方法或构造函数M1M2具有相同的类型参数,则以下两个条件均为真:1. M1M2具有相同数量的类型参数(可能为零)。2. 对于所有i(1≤i≤n)Ai的绑定与将Bi的约束应用于θ=[B1:=A1,...,Bn:=An]的绑定的结果相同。其中,A1,...,AnM的类型参数,B1,...,BnN的类型参数。"
"A.get2()"具有"public abstract "类型参数部分。但是在"public R B.get2()"中的"R"不是特定上下文中的类型参数。在该上下文中,"Double"是同名泛型方法在"A"中的调用(实例化)中的类型参数。"Double"是一个类型参数的类型参数实参。"link1"指向类型参数部分。
为了有一个符合JLS法规的重写(override)情况,必须满足两个方法必须JLS称之为重写等效的...

...如果两个方法签名M1M2是重写等效的,则M1M2的子签名或者M2M1的子签名...

两种方法不能被重写等效,除非其中一种是另一种的子签名...
引用: ... 方法M1的签名是方法M2的签名的子签名,如果以下情况之一: 1. M2与M1具有相同的签名,或者 2. M1的签名与M2的签名的擦除(§4.6)相同。
... 两种方法不能成为彼此的子签名,除非它们具有相同的签名...
“同一签名”的含义是如果两个方法具有相同的名称和参数类型,则它们具有相同的签名。你所说的两个方法并不具有相同的签名,因为它们没有满足JLS中“相同参数类型”的条件。”
两个方法或构造函数声明M和N具有相同的参数类型,如果满足以下所有条件: 1. 它们具有相同数量的形式参数(可能为零)。 2. 它们具有相同数量的类型参数(可能为零)。 3. 让A1,...,An是M的类型参数,并让B1,...,Bn是N的类型参数。在将N中每个出现的Bi重命名为Ai后,相应类型变量的边界相同,并且M和N的形式参数类型相同。
因此,为了使B.get2()编译——并且B.get()是100%类型安全——则需要更改方法以达到覆盖等效性。
最简单的方法是使它们具有与覆盖的A中的类型参数相同数量的类型参数...
import java.util.Collection;

public abstract class A {

    public abstract class R<T> {

    public abstract <X> R<X> get();

    public abstract <Y> R<Y> get2(Collection<String> p);

    public abstract <Z> R<Z> get3(Collection<Integer> p);

    public static class B extends A {

        @Override
        public <T> R<T> get() {
            return null;
        }

        @Override
        public <U> R<U> get2(Collection<String> p) {
            return null;
        }

        @Override
        public <V> R<V> get3(Collection<Integer> p) {
            return null;
        }
    }
}

0

尝试

...
public <X> R<X> get2(Collection<String> p){
...
public <X> R<X> get3(Collection<Integer> p) {
...

1
这并没有解释为什么 B.get2 没有覆盖 A.get2 - lexicore

0

您的X泛型类型并不是Object,而是一种extends 扩展自Object的类型,因为将一个Object替换成Object没有任何意义。您的get2方法的有效原型应该是这个:

// X or Y or any generic type or class
public <Y> R<Y> get2(Collection<String> p) {
    return null;
}

你的get3方法也是同样的情况,你不需要使用泛型来操作对象,应该使用扩展Object的方式。

public <Y> R<Y> get3(Collection<Integer> p) {
     return null;
}

这并没有解释为什么 B.get2 没有覆盖 A.get2 - lexicore

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