添加参数后,覆盖具有泛型返回类型的方法失败

lex*_*ore 17 java generics overriding

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

public abstract class A {

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

    public static class B extends A {

        @Override
        public Supplier<String> getSupplier() {
            return String::new;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

虽然这不是:

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

根据JLS§8.4.8.1,B.getSupplier必须是一个副主题A.getSupplier:

在类C中声明或继承的实例方法mC,覆盖C类中声明的另一个方法mA,iff以下所有条件都为真:

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

Subsignatures定义在JLS§8.4.2:

两种方法或构造,M和N,具有相同的签名,如果它们具有相同的名称,相同类型的参数(如果有的话)(§8.4.4),并且,调整所述形参类型的N到所述类型参数后M,相同的形式参数类型.

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

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

因此它似乎B.getSupplier是一个子签名A.getSupplier,但B.getSuppliers就是没有一个子签名A.getSuppliers.

我想知道情况如何.

如果B.getSupplierA.getSupplier因为它具有相同的擦除的子签名,那么B.getSuppliers也必须具有相同的擦除A.getSuppliers.这应该足以使压倒一切getSuppliers合法 - 但事实并非如此.

如果B.getSupplierA.getSupplier因为它具有相同的签名的子签名,那么我想知道"相同的类型参数(如果有的话)"究竟意味着什么.

如果考虑了类型参数,那么它们应该具有不同的类型参数:A.getSupplier具有类型参数X,B.getSupplier没有.
如果不考虑类型参数那么有何getSuppliers不同?

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

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

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

A b = new B();
URL url = b.<URL>getSupplier().get();
Run Code Online (Sandbox Code Playgroud)

Ole*_*hov 5

根据编译器输出,两个示例中的方法签名都不同(使用-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)
Run Code Online (Sandbox Code Playgroud)

根据JLS说明书中,方法m相关的签名1是一个子签名的方法m相关的签名的2如果任:

  • m 2与m 1具有相同的特征,

  • m 1的签名与m 2签名的擦除相同.

第一个声明不在游戏中 - 方法签名不同.但是第二个声明和删除呢?

有效覆盖

B.getSupplier()(m 1)是A.<X>getSupplier()(m 2)的子签名,因为:

  • m 1的签名与m 2签名的擦除相同

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

无效覆盖

B.getSuppliers(...)(m 1)不是A.<X>getSuppliers(...)(m 2)的子签名,因为:

  • m 1的签名与m 2的签名的擦除不同

m 1的签名:

getSuppliers(Collection<String> strings);
Run Code Online (Sandbox Code Playgroud)

擦除m 2的签名:

getSuppliers(Collection strings);
Run Code Online (Sandbox Code Playgroud)

将m 1参数从原点更改Collection<String>为原始Collection消除了错误,在这种情况下,m 1成为m 2的子签名.

结论

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

第二个代码片段(无效覆盖):方法签名最初不同,并且在将擦除应用于父方法后保持不同.