我不知道为什么这是有效的替代:

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.1B.getSupplier必须是子签名A.getSupplier:

子签名在JLS §8.4.2中定义:

  • m2与m1或
  • 具有相同的签名
  • m1的签名与m2的签名的擦除(第4.6节)相同。

  • 因此,似乎B.getSupplierA.getSupplier的子签名,但B.getSuppliers不是A.getSuppliers的子签名。
    我不知道怎么回事。
    如果B.getSupplierA.getSupplier的子签名,因为它具有相同的擦除,则B.getSuppliers也必须具有与A.getSuppliers相同的擦除。这应该足以覆盖getSuppliers合法-但事实并非如此。
    如果B.getSupplierA.getSupplier的子签名,因为它具有相同的签名,那么我想知道“相同类型参数(如果有)”到底是什么意思。
    如果考虑类型参数,则它们应具有不同的类型参数:A.getSupplier具有类型参数XB.getSupplier没有类型参数。
    如果不考虑类型参数,那么getSuppliers有何不同?
    这更多是关于重写和泛型的学术问题,因此请不要建议重构代码(例如将类型参数X移动到类等)。
    我正在寻找基于JLS的正式答案。
    从我的角度来看,B.getSupplier应该不能覆盖A.getSupplier,因为它们没有相同的类型参数。这使得以下代码(产生ClassCastException)合法:
    A b = new B();
    URL url = b.<URL>getSupplier().get();
    

    最佳答案

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

    第一条陈述是不合时宜的-方法签名是不同的。但是第二个陈述和删除呢?
    有效替代B.getSupplier()(m1)是A.<X>getSupplier()(m2)的子签名,因为:

    擦除后的<X>getSupplier()等于getSupplier()
    无效 override B.getSuppliers(...)(m1)不是A.<X>getSuppliers(...)(m2)的子签名,因为:

    m1的签名:
    getSuppliers(Collection<String> strings);
    
    删除m2的签名:
    getSuppliers(Collection strings);
    
    将m1参数从Collection<String>更改为原始Collection可消除错误,在这种情况下,m1成为m2的子签名。
    结论
    第一个代码段(有效覆盖):父类和子类中的方法签名最初是不同的。但是,在将擦除应用于父方法之后,签名变得相同。
    第二个代码段(无效覆盖):方法签名最初是不同的,并且在将擦除操作应用于父方法后仍保持不同。

    10-08 19:09