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
}
}
Service 中 someMethod 的目的是过滤支持的 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 方法中的推理是,如果 bundle 在 Service 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 不检查强制转换的解决方案是自己检查 :)
不好的命名请见谅,下面描述的问题是对真实代码的非常粗略的简化和改动
我定义了以下类型:
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
}
}
Service 中 someMethod 的目的是过滤支持的 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 方法中的推理是,如果 bundle 在 Service 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 不检查强制转换的解决方案是自己检查 :)