避免在具有多种值类型的地图中取消选中分配?
我在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
。 然后,隐式检查转换实际发生在返回值上 ,将其转换为Integer
并ClassCastException
。
这种奇怪的交互是由于类型擦除 。
因此,当我们在单个数据结构中存储多个类型时,有多种方法可以处理它,具体取决于我们的需求。
如果没有办法执行,我们可以放弃编译检查。
在这里存储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); } }
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");
在这两种情况下,我们只是依靠自己作为程序员的意识来避免犯错误。
抑制警告并不一定是坏事,了解它们实际意味着什么以及其含义是非常重要的。
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 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 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(以及任何其他强类型语言)无法以静态类型安全的方式表达地图的值类型。
这是因为元素类型仅在运行时已知。 期望编译器静态地检查尚未知道的东西是太多了。 编译器甚至无法进行合理的静态类型推断,因此期望它预测动态类型推断的未来实际上是一个延伸。