为什么仅当基方法包含未使用的类型参数时,重写此泛型方法才有效?



我在做Java项目时遇到了一件相当奇怪的事情。

我在interface中有这个基本方法:

public interface Registry<T extends FlakeType<? extends F>, F extends Flake> {
@Nullable
public <E extends F> T findType(@Nonnull String name);
}

显然,类型参数E是完全无用的,因为它既不用于返回类型,也不用于参数。我的 IDE 也会通知我这一点。

我还有另一个扩展Registry的界面:

public interface EntityRegistry extends Registry<EntityType<? extends Entity>, Entity> {
@Nullable
@Override
<E extends Entity> EntityType<E> findType(@Nonnull String name);
}

对于上下文,Entity扩展FlakeEntityType<E extends Entity>扩展FlakeType<E>

如您所见,我倾向于指定此方法的返回类型,从EntityType<? extends Entity>(此类型来自T类型参数(到EntityType<E>,而E是方法类型参数<E extends Entity>

从逻辑上讲,在这种情况下,? extends EntityE extends Entity的意思相同,因为E没有其他限制或类似的东西,因此本质上也充当扩展Entity类型的通配符。

这工作得很好。


但是,我注意到我的IDE(Intellij(的通知

从不使用类型参数E

这让我觉得我可以删除它,因为它显然是无用的,并且与返回值或任何参数没有任何关系。

重写方法提供自己的E类型参数,该参数不应与基方法中具有相同名称的无用类型参数有任何关系。还是有?

因为一旦我将基本方法更改为:

@Nullable
public T findType(@Nonnull String name);

不再可能以相同的方式覆盖它:

findType(String)EntityRegistry冲突findType(String)Registry;两种方法具有相同的擦除,但 两者都不能凌驾于另一方之上。


我现在的问题是:

  • 为什么 Java 在基方法中需要一个无用的类型参数,当 重写方法想要指定返回值泛型类型?重写方法提供的参数不应该足够吗?

  • 为什么在第二种情况下覆盖不起作用?它不应该工作,因为签名与第一种情况完全相同,只是使用了另一种类型参数而不是显式通配符,但由于这确实意味着同样的事情,有什么区别?

我确信我对泛型做出了一些错误的假设,这就是我对此感到困惑的原因。你能帮我清理一下吗?

提前感谢!

类型参数是方法签名的一部分。您可以在 JLS §8.4.2 中找到它:

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

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

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

  • m1的签名与m2签名的删除(§4.6(相同。

两个方法签名m1m2是覆盖等效的,m1要么是m2的子签名,要么是m2m1的子签名。

如果没有Registry中的 type 参数,EntityRegistry中的方法没有基接口方法的子签名,因此它不被视为覆盖。

请参阅Jorn Vernee的回答,以获取有关编译失败原因的完整描述:

类型参数是方法签名的一部分。

要使其编译,只需在EntityRegistry中指定方法为:

@Nullable
@Override
EntityType<? extends Entity> findType(@Nonnull String name);

这与原始版本一致,因为E确实是未定义的,即?.

有效覆盖 -findType方法的签名在两个接口中相同:

<E>findType(String name)

无效覆盖 - 由于删除了类型参数,签名变得不同E

<E>findType(String name)
findType(String name)

JLS §8.4.8.1 和 §8.4.2 描述了覆盖规则以及方法签名在其中的作用。

原始代码的一个重要部分是它不必要地复杂。此外,如果不满足于未经检查的演员阵容,就无法实现您的原始EntityRegistry.findType()......

...warning: [unchecked] unchecked cast
return ( EntityType< E > ) new EntityType< >(  new Entity( name ) );
^
required: EntityType<E>
found:    EntityType<Entity>
where E is a type-variable:
E extends Entity declared in method <E>findType(String)
1 warning

更糟糕的是,由于EntityRegistry.findType()是一种通用方法,因此不能用作 lambda

这是一个更简单、更安全的解决方案

interface Registry< T extends FlakeType<? extends Flake> > {
T findType(String name);
}
interface EntityRegistry< U extends EntityType<? extends Entity> > extends Registry<U> {
@Override
U findType(String name);
}

上面链接中的成功编译、正确运行的实现是这样工作的......

...
EntityRegistry<EntityType<Entity>> registry = (name) -> { return new EntityType<Entity>(new Entity(name)); };
...

这样...

...
Registry<EntityType<Entity>> registry = (name) -> { return new EntityType<Entity>(new Entity(name)); };
...

最新更新