避免在具有多种值类型的地图中取消选中分配?

我在Java 7中遇到警告时遇到问题:

Unchecked assignment: 'java.lang.Class' to 'java.lang.Class' 

我在Class type = typeMap.get(key);行上得到它Class type = typeMap.get(key); 在下面的get函数中。

基本上我在这里想要做的是我想存储一堆未知类型的键/值对(但是所有都是Object的后代,除了null),但不会丢失类型。 所以我使用generics创建了一个包含以下内容的类。 它有两个映射(一个用于存储数据,另一个用于存储类类型:

  private Map dataMap = new HashMap(); private Map typeMap = new HashMap(); public  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 get(String key){ Class type = typeMap.get(key); if (type == null){ return null; } return type.cast(dataMap.get(key)); } 

它运行得很好,但警告让我烦恼。 有没有办法让Java在没有抱怨的情况下进行此演员(除了抑制它)? 或者有更好的方法来完成我想要做的事情吗? 在Java 8中怎么样,因为我还没有机会潜入它呢?

谢谢!

你所展示的不安全的原因是这项任务:

 Class 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进行了未经检查的转换。 对type.cast(...)的调用不会抛出,因为从数据映射中检索的值是String 。 然后,隐式检查转换实际发生在返回值上 ,将其转换为IntegerClassCastException

这种奇怪的交互是由于类型擦除 。


因此,当我们在单个数据结构中存储多个类型时,有多种方法可以处理它,具体取决于我们的需求。

如果没有办法执行,我们可以放弃编译检查。

在这里存储Class是没有意义的,因为正如我在上面所示,它没有执行有用的validation。 所以我们可以按照以下几行重新设计地图:

 class Example { private final Map m = new HashMap<>(); void put(String k, Object v) { m.put(k, v); } Object getExplicit(String k) { return m.get(k); } @SuppressWarnings("unchecked")  T getImplicit(String k) { return (T) m.get(k); } } 

getExplicitgetImplicit做了类似的事情但是:

 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"); 

在这两种情况下,我们只是依靠自己作为程序员的意识来避免犯错误。

抑制警告并不一定是坏事,了解它们实际意味着什么以及其含义是非常重要的。

2.传递一个Class以使得返回值必须有效。

这是我通常看到的方式。

 class Example { private final Map m = new HashMap<>(); void put(String k, Object v) { m.put(k, v); }  T get(String k, Class 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

3.参数化键。

这是一个新颖但更先进的,它可能会或可能不会更方便。

 class Example { private final Map, Object> m = new HashMap<>();  Key put(String s, V v) { Key k = new Key<>(s, v); put(k, v); return k; }  void put(Key k, V v) { m.put(k, v); }  V get(Key k) { Object v = m.get(k); return kcisInstance(v) ? kccast(v) : null; } static final class Key { private final String k; private final Class 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) v.getClass()); } Key(String k, Class 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 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 = new Key<>("foo", Foo.class); static final Key BAR = new Key<>("bar", Bar.class); } 

然后,我们不必在检索完成时构造关键对象。 这非常适用于为字符串类型的场景添加一些强类型。

 Foo foo = example.get(Keys.FOO); 

4.没有地图可以放入任何类型的对象,使用某种多态。

如果可能而且不太麻烦,这是一个不错的选择。 如果存在使用不同类型的常见行为,请将其设置为接口或超类,以便我们不必使用强制转换。

一个简单的例子可能是这样的:

 // bunch of stuff Map 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 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(); 

您正在尝试将类型为Class的元素分配给Class类型的变量。 当然,这是一项未经检查的任务。 您似乎正在实现异构映射。 Java(以及任何其他强类型语言)无法以静态类型安全的方式表达地图的值类型。

这是因为元素类型仅在运行时已知。 期望编译器静态地检查尚未知道的东西是太多了。 编译器甚至无法进行合理的静态类型推断,因此期望它预测动态类型推断的未来实际上是一个延伸。