为什么仅在基本方法包含未使用的类型参数的情况下才覆盖此通用方法?

时间:2018-08-23 21:50:02

标签: java generics override

在处理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>,而{{1 }}是方法类型参数E

从逻辑上讲,<E extends Entity>? extends Entity在这种情况下具有相同的含义,因为E extends Entity没有其他限制或类似限制,因此从本质上讲,它还充当类型扩展的通配符E

这很好用。


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

  

从未使用过类型参数Entity

这使我认为我可以删除它,因为它显然没有用,并且与返回值或任何参数都没有任何关系。

重载方法提供了自己的E类型参数,该类型参数绝不应与基本方法中同名的无用类型参数有关。 还是有?

因为我将基本方法更改为此:

E

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

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

findType(String)与   EntityRegistry中的findType(String);两种方法的擦除相同   都不会覆盖另一个。


我的问题是:

  • 为什么Java在基本方法中需要无用的类型参数? 一个覆盖方法想指定返回值的泛型类型?覆盖方法提供的参数不足够吗?

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

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

谢谢!

4 个答案:

答案 0 :(得分:2)

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

  

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

     

方法m1的签名是方法m2的签名的子签名,如果有以下情况:

     
      
  • m2m1具有相同的签名,或者

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

  •   
     

如果m1m2m1的子签名,则两个方法签名m2m2等效的m1的子签名。

Registry中没有类型参数的情况下,EntityRegistry中的方法没有基本接口方法的子签名,因此不被视为替代。

答案 1 :(得分:1)

请参阅answer by Jorn Vernee,以获取有关为何无法编译的完整说明:

  

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

要使其编译,只需在@Nullable @Override EntityType<? extends Entity> findType(@Nonnull String name); 中将方法指定为:

E

这与原始版本一致,因为?实际上是未定义的,即if ($('h3').text().includes('®')) { // Do stuff }

答案 2 :(得分:1)

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

<E>findType(String name)

无效的替代-由于移除了类型参数E,因此签名变得不同:

<E>findType(String name)

findType(String name)

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

答案 3 :(得分:1)

原始代码问题的很大一部分是不必要的复杂。另外,如果不解决未经检查的强制转换,就无法实现原始的 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() 是一种通用方法it can't be used as a lambda

这里是a much simpler, more type-safe solution

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)); };
...