我正在寻找一种“正确”的方法来减少编译时检索/修改泛型类型参数所涉及的 Java 样板。通常,此样板涉及:
- 使用
@SuppressWarnings("unchecked")
. - 明确拼写出目标泛型类型参数。
- 通常,创建一个原本无用的局部变量,以便仅将抑制应用于该语句。
作为一个理论示例,假设我想保留 Class
的 map 至Supplier
这样对于每个 keyClass
,其关联valueSupplier
生成扩展 keyClass
的对象.
编辑 2:我从 Class
的 map 更改了示例至Class
,到 Class
的 map 至Supplier
,因为(值)Class
对象对于强制转换而言是特殊的,原始示例有另一个不涉及未经检查的强制转换的解决方案(感谢@Holger)。再次强调,我只是添加一个示例来说明问题,我不需要解决任何特定的示例。
编辑 1:更准确地说,是单个 SupplierMap
对象是从配置文件填充的,并保存诸如“实现接口(interface) I1 的对象由供应商 S1 提供”、“I2 由 S2 提供”等信息。在运行时,我们会收到诸如 I1 i1 = supplierMap.get(I1.class).get()
之类的调用它应该生成一个带有 i1.getClass() == C1.class
的对象。我对修复/快捷方式不感兴趣,例如将强制转换移动到不属于的位置,例如 get()
返回Supplier<Object>
。从概念上讲, Actor 阵容属于 SupplierMap
。另外,我不太关心这个具体的例子,而是关心一般的语言问题。
与SupplierMap
,我不相信有一种方法可以捕获Java中的键值通用参数关系,以便get()
不涉及未经检查的编译时转换。具体来说,我可以:
class SupplierMap {
// no way to say in Java that keys and values are related
Map<Class<?>, Supplier<?>> map;
// can check the relation at compile time for put()
<T> void put(Class<T> keyClass, Supplier<? extends T> valueSupplier) {
map.put(keyClass, valueSupplier);
}
// but not for get()
<T> Supplier<? extends T> get(Class<T> keyClass) {
@SuppressWarnings("unchecked")
final Supplier<? extends T> castValueSupplier = (Supplier<? extends T>) map.get(keyClass);
return castValueSupplier;
}
}
作为一种替代方案,人们可以:
@SupressWarnings("unchecked")
<T> T uncheckedCast(Object o) {
return (T) o;
}
<T> Supplier<? extends T> get(Class<T> keyClass) {
return uncheckedCast(map.get(keyClass));
}
看起来好多了,但问题是uncheckedCast
可以说太强大了:它可以在编译时将任何东西转换成其他东西(通过隐藏警告)。在运行时我们仍然会得到 CCE,但这不是重点。 (论点是......)如果有人把这个uncheckedCast
如果将其放入库中,该函数可能会被滥用来隐藏在编译时可检测到的问题。
有没有办法定义这样一个类似的未经检查的强制转换函数,以便编译器可以强制它仅用于更改泛型类型参数?
我尝试过:
// error at T<U>: T does not have type parameters
<T, U> T<U> uncheckedCast(T t) {
return (T<U>) t;
}
还有
<T, U extends T> U uncheckedCast(T t) {
return (U) t;
}
void test() {
Class<?> aClass = String.class;
// dumb thing to do, but illustrates cast error:
// type U has incompatible bounds: Class<capture of ?> and Class<Integer>
Class<Integer> iClass = uncheckedCast(aClass);
}
编辑:您在公共(public)库中见过这种未经检查的 Actor (甚至是上面的全能 Actor )吗?我查看了 Commons Lang 和 Guava,但我唯一能找到的是 Chronicle Core 的 ObjectUtils.convertTo()
:那里,路过eClass == null
相当于全能的uncheckedCast
,除了它还会产生不需要的 @Nullable
(由其他分支使用)。
最佳答案
你说
Also, I don't much care about this specific example, but about the general language problem.
但实际上,此类问题应该始终结合您要解决的实际问题来处理。我想说的是,这种情况很少发生,因此无论实际用例如何,进行未经检查的强制转换的通用实用方法都是不合理的。
考虑到SupplierMap
,你说
I don't believe there is a way to capture the key-value generic parameter relation in Java so that the
get()
does not involve an unchecked compile-time cast.
这就是解决问题并指出干净的解决方案。您必须在键和值之间创建关系,例如
class SupplierMap {
static final class SupplierHolder<T> {
final Class<T> keyClass;
final Supplier<? extends T> valueSupplier;
SupplierHolder(Class<T> keyClass, Supplier<? extends T> valueSupplier) {
this.keyClass = keyClass;
this.valueSupplier = valueSupplier;
}
@SuppressWarnings("unchecked") // does check inside the method
<U> SupplierHolder<U> cast(Class<U> type) {
if(type != keyClass) throw new ClassCastException();
return (SupplierHolder<U>)this;
}
}
Map<Class<?>, SupplierHolder<?>> map = new HashMap<>();
<T> void put(Class<T> keyClass, Supplier<? extends T> valueSupplier) {
map.put(keyClass, new SupplierHolder<>(keyClass, valueSupplier));
}
<T> Supplier<? extends T> get(Class<T> keyClass) {
return map.get(keyClass).cast(keyClass).valueSupplier;
}
}
这里,未经检查的类型转换位于执行实际检查的方法内,使读者对操作的正确性充满信心。
这是实际代码中实际使用的模式。这有望解决您的问题“您在公共(public)库中见过这种未经检查的 Actor (甚至是上面的全能 Actor )吗?”。我不认为任何库都使用“完全未经检查”的方法,相反,它们的方法具有尽可能窄的可见性,并且可能是根据实际用例量身定制的。
是的,这意味着“样板文件”。这对于开发人员在继续之前应该花几秒钟的操作来说还不错。
请注意,这可以扩展到完全可以工作的示例,而无需使用 Class
作为标记:
interface SomeKey<T> {}
class SupplierMap {
static final class SupplierHolder<T> {
final SomeKey<T> keyClass;
final Supplier<? extends T> valueSupplier;
SupplierHolder(SomeKey<T> keyToken, Supplier<? extends T> valueSupplier) {
this.keyClass = keyToken;
this.valueSupplier = valueSupplier;
}
@SuppressWarnings("unchecked") // does check inside the method
<U> SupplierHolder<U> cast(SomeKey<U> type) {
if(type != keyClass) throw new ClassCastException();
return (SupplierHolder<U>)this;
}
}
Map<SomeKey<?>, SupplierHolder<?>> map = new HashMap<>();
<T> void put(SomeKey<T> keyClass, Supplier<? extends T> valueSupplier) {
map.put(keyClass, new SupplierHolder<>(keyClass, valueSupplier));
}
<T> Supplier<? extends T> get(SomeKey<T> keyClass) {
return map.get(keyClass).cast(keyClass).valueSupplier;
}
}
允许多个相同类型的 key :
enum MyStringKeys implements SomeKey<String> {
SAY_HELLO, SAY_GOODBYE
}
public static void main(String[] args) {
SupplierMap m = new SupplierMap();
m.put(MyStringKeys.SAY_HELLO, () -> "Guten Tag");
m.put(MyStringKeys.SAY_GOODBYE, () -> "Auf Wiedersehen");
System.out.println(m.get(MyStringKeys.SAY_HELLO).get());
Supplier<? extends String> s = m.get(MyStringKeys.SAY_GOODBYE);
String str = s.get();
System.out.println(str);
}
关键的部分是,现在不可避免的未经检查的强制转换仍然通过对 key 有效性的实际检查来增强。如果没有,我绝对不会允许这样做。
这并不排除您根本无法检查正确性的情况。但是,最好认为像 @SuppressWarnings("unchecked") 注释这样的详细工件在需要的地方表明了这一点。便捷方法只会隐藏问题,即使我们有可能将其使用限制为泛型类型,问题仍然存在。
<小时/>对问题先前修订版的回答:
这实际上比您想象的要容易:
class ImplMap {
Map<Class<?>, Class<?>> map;
<T> void put(Class<T> keyClass, Class<? extends T> valueClass) {
map.put(keyClass, valueClass);
}
<T> Class<? extends T> get(Class<T> keyClass) {
final Class<?> implClass = map.get(keyClass);
return implClass.asSubclass(keyClass);
}
}
这不是一个未经检查的操作,如方法asSubclass
真正检查 implClass
类是否是 keyClass
的子类。假设仅通过 put
方法填充了 map ,没有任何未经检查的操作,则此测试永远不会失败。
唯一不同的是对 null
的处理,例如当 key 不在 map 中时。与强制转换不同,这会引发异常,因为它是方法调用。
因此,如果允许使用不存在的键调用此方法并且会导致 null
,则必须显式处理:
<T> Class<? extends T> get(Class<T> keyClass) {
final Class<?> implClass = map.get(keyClass);
return implClass == null? null: implClass.asSubclass(keyClass);
}
请注意,同样,该方法
@SupressWarnings("unchecked")
<T> T uncheckedCast(Object o) {
return (T) o;
}
如果您有 Class
对象,则 是不必要的,这样您就可以调用 cast
就在上面。
例如,以下内容将是对 ImplMap
类的有效添加:
<T> T getInstance(Class<T> keyClass) {
try {
return keyClass.cast(map.get(keyClass).getConstructor().newInstance());
} catch (ReflectiveOperationException ex) {
throw new IllegalStateException(ex);
}
}
<小时/>
作为补充说明,通过配置文件注册接口(interface)实现听起来您应该看看 ServiceLoader
API 和底层机制。另请参阅Creating Extensible Applications Java 教程的章节。
关于java - 如何封装未经检查的强制转换以仅允许未经检查的泛型类型转换?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57899235/