使用泛型返回类型重写方法失败,添加参数后

2022-09-03 02:54:23

我想知道为什么这是一个有效的覆盖:

public abstract class A {

    public abstract <X> Supplier<X> getSupplier();

    public static class B extends A {

        @Override
        public Supplier<String> getSupplier() {
            return String::new;
        }
    }
}

而这不是:

public abstract class A {

    public abstract <X> Supplier<X> getSuppliers(Collection<String> strings);

    public static class B extends A {

        @Override
        public Supplier<String> getSuppliers(Collection<String> strings) {
            return String::new;
        }
    }
}

根据 JLS §8.4.8.1,必须是子签名:B.getSupplierA.getSupplier

在类 C 中声明或由类 C 继承的实例方法 mC,从 C 重写在类 A 中声明的另一个方法 mA,如果以下所有条件都为真:

  • ...
  • mC 的签名是 mA 签名的子签名 (§8.4.2)。
  • ...

子签名在 JLS §8.4.2 中定义:

如果两个方法或构造函数(M 和 N)具有相同的名称、相同的类型参数(如果有)(§8.4.4),并且 N 的形式参数类型适应 M 的类型参数后,具有相同的形式参数类型,则它们具有相同的签名。

方法 m1 的签名是方法 m2 签名的子签名,如果出现以下任一情况:

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

因此,它似乎是 的子签名,但不是 的子签名。B.getSupplierA.getSupplierB.getSuppliersA.getSuppliers

我想知道怎么会这样。

如果 是 的子签名,因为它具有相同的擦除,则还必须具有 与 相同的擦除。这应该足以使推翻是合法的 - 但事实并非如此。B.getSupplierA.getSupplierB.getSuppliersA.getSuppliersgetSuppliers

如果 是 一个子签名,因为它具有相同的签名,那么我想知道“相同类型参数(如果有的话)”到底是什么意思。B.getSupplierA.getSupplier

如果考虑类型参数,那么它们应该具有不同的类型参数:具有类型参数 ,没有。
如果不考虑类型参数,那么有什么不同?A.getSupplierXB.getSuppliergetSuppliers

这更像是一个关于重写和泛型的学术问题,所以请不要建议重构代码(比如将类型参数移动到类等)。X

我正在寻找一个正式的,基于JLS的答案。

从我的角度来看,应该不能覆盖,因为它们没有相同的类型参数。这使得以下代码(产生)合法:B.getSupplierA.getSupplierClassCastException

A b = new B();
URL url = b.<URL>getSupplier().get();

答案 1

根据编译器输出,两个示例中的方法签名不同(编译代码并带有确认选项):-Xlint:unchecked

<X>getSupplier() in A (m2)
                                 1st snippet
getSupplier()    in B (m1)


<X>getSuppliers(Collection<String> strings) in A (m2)
                                                           2nd snippet
getSuppliers(Collection<String> strings)    in B (m1)

根据 JLS 规范,方法 m1 的签名是方法 m2 签名的子签名,如果出现以下任一情况:

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

  • m1 的签名与 m2 签名的擦除相同。

第一个语句不在游戏中 - 方法签名是不同的。但是第二个语句和擦除呢?

有效覆盖

B.getSupplier()(m1) 是 (m2) 的子签名,因为:A.<X>getSupplier()

  • m1 的签名与 m2 签名的擦除相同

<X>getSupplier()擦除后等于 。getSupplier()

无效的覆盖

B.getSuppliers(...)(m1) 不是 (m2) 的子签名,因为:A.<X>getSuppliers(...)

  • m1 的签名与 m2 的签名的擦除不同

m1 的签名:

getSuppliers(Collection<String> strings);

删除 m2 的签名:

getSuppliers(Collection strings);

将 m1 参数从更改为 raw 可消除错误,在这种情况下,m1 成为 m2 的子签名。Collection<String>Collection

结论

第一个代码片段(有效重写):父类和子类中的方法签名最初是不同的。但是,在将擦除应用于父方法后,签名将变得相同。

第二个代码片段(无效重写):方法签名最初是不同的,并且在将擦除应用于父方法后仍然不同。


答案 2

添加参数的那一刻,它就不再是一个覆盖,而变成了一个重载。

泛型与它无关。


推荐