我在Java 7中遇到警告问题:
Unchecked assignment: 'java.lang.Class' to 'java.lang.Class<T>'
我在下面的get函数的Class<T> type = typeMap.get(key);
行上了。
基本上我在这里要做的是我想存储一堆未知类型的键/值对(但是所有都是Object的后代,除了null),但不会丢失类型。所以我使用泛型创建了一个包含以下内容的类。它有两个映射(一个用于存储数据,另一个用于存储类类型:
private Map<String, Object> dataMap = new HashMap<>();
private Map<String, Class> typeMap = new HashMap<>();
public <T> void put(String key, T instance){
dataMap.put(key, instance);
if (instance == null){
typeMap.put(key,null);
}
else {
typeMap.put(key, instance.getClass());
}
}
public <T> T get(String key){
Class<T> type = typeMap.get(key);
if (type == null){
return null;
}
return type.cast(dataMap.get(key));
}
它运行得很好,但警告让我烦恼。有没有办法让Java在没有抱怨的情况下做这个演员(除了抑制它)?还是有更好的方法来完成我想要做的事情?在Java 8中,我还没有机会深入研究它?
谢谢!
答案 0 :(得分:33)
您所展示的不安全的原因是这项任务:
Class<T> type = typeMap.get(key);
T
不需要与从地图中检索到的Class
有任何关系。始终从T
调用的周围环境推断出get
。例如,我可以执行以下调用序列:
// T is inferred from the arguments as String (which is fine)
example.put("k", "v");
// T is inferred from the return value target type as Integer
Integer i = example.get("k");
在get
方法中,从类型地图中正确检索了String.class
,但未经检查的转换是Class<Integer>
。对type.cast(...)
的调用不会抛出,因为从数据映射中检索的值是String
。隐式检查转换然后实际发生在返回值上,将其转换为Integer
并抛出ClassCastException
。
这种奇怪的互动归因于type erasure。
因此,当我们在单个数据结构中存储多个类型时,有很多方法可以处理它,具体取决于我们的需求。
存储Class
对于大多数人来说毫无意义,因为正如我上面所示,它并没有执行有用的验证。所以我们可以按照以下几行重新设计地图:
class Example {
private final Map<String, Object> m = new HashMap<>();
void put(String k, Object v) {
m.put(k, v);
}
Object getExplicit(String k) {
return m.get(k);
}
@SuppressWarnings("unchecked")
<T> T getImplicit(String k) {
return (T) m.get(k);
}
}
getExplicit
和getImplicit
做了类似的事情,但是:
String a = (String) example.getExplicit("k");
// the generic version allows an implicit cast to be made
// (this is essentially what you're already doing)
String b = example.getImplicit("k");
在这两种情况下,我们只是依靠自己作为程序员的意识来避免犯错。
抑制警告并不一定是坏事,了解它们的真正含义及其含义是非常重要的。
Class
传递给get
,以使返回值有效。这是我通常看到的方式。
class Example {
private final Map<String, Object> m = new HashMap<>();
void put(String k, Object v) {
m.put(k, v);
}
<T> T get(String k, Class<T> c) {
Object v = m.get(k);
return c.isInstance(v) ? c.cast(v) : null;
}
}
example.put("k", "v");
// returns "v"
String s = example.get("k", String.class);
// returns null
Double d = example.get("k", Double.class);
但是,当然,这意味着我们需要将两个参数传递给get
。
这是一部小说但更先进的,它可能会或可能不会更方便。
class Example {
private final Map<Key<?>, Object> m = new HashMap<>();
<V> Key<V> put(String s, V v) {
Key<V> k = new Key<>(s, v);
put(k, v);
return k;
}
<V> void put(Key<V> k, V v) {
m.put(k, v);
}
<V> V get(Key<V> k) {
Object v = m.get(k);
return k.c.isInstance(v) ? k.c.cast(v) : null;
}
static final class Key<V> {
private final String k;
private final Class<? extends V> c;
@SuppressWarnings("unchecked")
Key(String k, V v) {
// this cast will always be safe unless
// the outside world is doing something fishy
// like using raw types
this(k, (Class<? extends V>) v.getClass());
}
Key(String k, Class<? extends V> c) {
this.k = k;
this.c = c;
}
@Override
public int hashCode() {
return k.hashCode();
}
@Override
public boolean equals(Object o) {
return (o instanceof Key<?>) && ((Key<?>) o).k.equals(k);
}
}
}
所以例如:
Key<Float> k = example.put("k", 1.0f);
// returns 1.0f
Float f = example.get(k);
// returns null
Double d = example.get(new Key<>("k", Double.class));
如果条目是已知的或可预测的,这可能是有意义的,所以我们可以有类似的东西:
final class Keys {
private Keys() {}
static final Key<Foo> FOO = new Key<>("foo", Foo.class);
static final Key<Bar> BAR = new Key<>("bar", Bar.class);
}
然后,我们不必在检索完成时构建关键对象。这非常适用于为字符串类型的场景添加一些强类型。
Foo foo = example.get(Keys.FOO);
如果可能而且不太麻烦,这是一个不错的选择。如果存在使用不同类型的常见行为,请将其设置为接口或超类,以便我们不必使用强制转换。
一个简单的例子可能是这样的:
// bunch of stuff
Map<String, Object> map = ...;
// store some data
map.put("abc", 123L);
map.put("def", 456D);
// wait awhile
awhile();
// some time later, consume the data
// being particular about types
consumeLong((Long) map.remove("abc"));
consumeDouble((Double) map.remove("def"));
我们可以用这样的东西代替:
Map<String, Runnable> map = ...;
// store operations as well as data
// while we know what the types are
map.put("abc", () -> consumeLong(123L));
map.put("def", () -> consumeDouble(456D));
awhile();
// consume, but no longer particular about types
map.remove("abc").run();
map.remove("def").run();
答案 1 :(得分:4)
您正在尝试将类型为Class
的元素分配给Class<T>
类型的变量。当然,这是一项未经检查的任务。您似乎正在实现异构映射。 Java(以及任何其他强类型语言)无法以静态类型安全的方式表达地图的值类型。
这是因为元素类型仅在运行时已知。期望编译器静态地检查尚未知道的东西是太过分了。编译器甚至无法进行合理的静态类型推断,因此期望它预测动态类型推断的未来真的是一个延伸。