Java 泛型:摆脱未经检查的投射警告(案例在里面描述)

Java generics: getting rid unchecked cast warning (case described inside)

不好的命名请见谅,下面描述的问题是对真实代码的非常粗略的简化和改动

我定义了以下类型:

interface Bundle {
  // omitted
}
interface Content<T extends Bundle> {
  void useBundle(T bundle);
  Class<? extends T> getSupportedBundleClass();
  // omitted
}
class Service<T extends Bundle> {
  private final T bundle;
  
  public Collection<Content<? super T>> someMethod(Collection<Object> allContents) {
    // omitted
  }  
}

ServicesomeMethod 的目的是过滤支持的 Content 实例捆绑包 class 与 捆绑包 的 class 相同或更高层级。

例如:

鉴于 Bundle 的层次结构...

interface Bundle {}
interface A extends Bundle {}
class B implements A {}

...这个设置,...

Content<Bundle> contentBundle = // omitted
Content<A> contentA = // omitted
Content<B> contentB = // omitted

Service<Bundle> serviceBundle = // omitted
Service<A> serviceA = // omitted
Service<B> serviceB = // omitted

List<Object> allContents = List.of(contentBundle, contentA, contentB);

...以下为真:

serviceBundle.someMethod(allContents) => [contentBundle]
serviceA.someMethod(allContents) => [contentBundle, contentA]
serviceB.someMethod(allContents) => [contentBundle, contentA, contentB]

下面是 someMethod 的实现方式(使用辅助方法进行转换和解析通配符):

public Collection<Content<? super T>> someMethod(Collection<Object> allContents) {
  return allContents.stream()
    .map(obj -> obj instanceof Content ? (Content<? extends Bundle>) obj : null)   // cast #1
    .filter(Objects:nonNull)
    .map(this::castOrNull)
    .filter(Objects::nonNull)
    .collect(Collectors.toList());
}

private <U extends Bundle> Content<? super T> castOrNull(Content<U> content) {
  return content.getSupportedBundleClass().isInstance(bundle) ? (Content<? super T>) content : null;   // cast #2
}  

我在 castOrNull 方法中的推理是,如果 bundleService class 是提供的 Content 实例的 支持的包 class 的实例,然后提供的 Content的类型参数是T的超类型(bundle的类型)。

someMethod 正常工作,正如预期的那样(如上例所述)。但是,我在带有注释 **// cast #2** 的行上收到未经检查的转换警告 Content<U>Content<? super T> (带有 [= 的行40=]**// cast #1** 没问题)。有没有办法消除警告(当然,除了抑制它)?

这种未经检查的转换是不可避免的。您正在将不涉及类型变量的内容转换为涉及类型变量的内容。要检查此转换的有效性,JVM 需要知道类型变量 T 持有什么类型。不幸的是没有。无论您将 Object 转换为哪种中间类型,您都会在某个时刻引入类型变量 T,即当运行时无法检查转换的有效性时,转换将被标记为未经检查的转换 - 运行时不会对此转换做任何事情,并且它可能实际上是无效的,并且稍后一切都会爆炸。

当然也可以引入自己的检查,比如

content.getSupportedBundleClass().isInstance(bundle)

但请注意,这与 JVM 会完成的检查有很大不同,用于检查 (Content<? super T>) content 的有效性,具有 [=13] 的类型=] 在运行时可用。特别是,您的检查取决于 Content 的实施者正确实施 getSupportedBundleClass,返回封闭 class,而不是封闭 class 的某些子 class:

class ContentBundle implements Content<Bundle> {
    @Override
    public void useBundle(Bundle bundle) {
        
    }

    @Override
    public Class<? extends Bundle> getSupportedBundleClass() {
        return B.class;
    }
}

如果 getSupportedBundleClass 返回 Class<T>,这将不是问题。

我不知道字段 bundle 是如何初始化的,所以我不知道这是否会发生,但您的检查还取决于 Service.bundle 始终是 T,而不是 T 的子 class。如果 serviceBundle.bundle 实际上存储了 B 的实例,那么 serviceBundle.someMethod(allContents) 将包含所有 3 个内容。

我建议您使用 Class<T> 来存储类型信息:

private final Class<T> bundleType;
public Service(Class<T> bundleType) {
    this.bundleType = bundleType;
}

并使用 isAssignableFrom 而不是 isInstance

如果您认为自己的检查就足够了(例如,如果您确定每个人都会以预期的方式实施 getSupportedBundleClass),那么 正在做检查转换的工作,你不应该担心 JVM 无法检查它。压制一下就好了

在大多数情况下,JVM 不检查强制转换的解决方案是自己检查 :)