Java 避免在具有多种值类型的映射中进行未经检查的赋值?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/22467645/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Avoid unchecked assignment in a map with multiple value types?
提问by irotsoma
I'm having trouble with a warning in Java 7:
我在 Java 7 中遇到警告问题:
Unchecked assignment: 'java.lang.Class' to 'java.lang.Class<T>'
I'm getting it on the line Class<T> type = typeMap.get(key);
in the get function below.
我在Class<T> type = typeMap.get(key);
下面的 get 函数中得到了它。
Basically what I'm trying to do here is I want to store a bunch of key/value pairs of unknown types (but all are descendants of Object with the exception of null), but not lose the type. So I created a class with the following content using generics. It has two maps (one to store the data and one to store the class type:
基本上我想在这里做的是我想存储一堆未知类型的键/值对(但除了 null 之外都是 Object 的后代),但不会丢失类型。所以我使用泛型创建了一个包含以下内容的类。它有两个映射(一个用于存储数据,另一个用于存储类类型:
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));
}
It runs just fine, but the warning is annoying me. Is there any way to get Java to do this cast without complaining (other than suppressing it)? Or is there a better way to accomplish what I'm trying to do? How about in Java 8 as I haven't really had a chance to dive into it yet?
它运行得很好,但警告让我很烦。有什么方法可以让 Java 不抱怨地执行此转换(除了抑制它)?或者有没有更好的方法来完成我想要做的事情?在 Java 8 中怎么样,因为我还没有真正有机会深入研究它?
Thanks!
谢谢!
采纳答案by Radiodef
The reason what you've shown is unsafe is that with this assignment:
你所展示的不安全的原因是这个任务:
Class<T> type = typeMap.get(key);
T
does not need to have anything to do with the Class
retrieved from the map. T
is always inferred from the surrounding context of the call to get
. For example I can do this sequence of calls:
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");
Inside the get
method, String.class
is correctly retrieved from the type map, but an unchecked conversion is made to Class<Integer>
. The call to type.cast(...)
doesn't throw, because the value retrieved from the data map is a String
. An implicit checked cast then actuallyhappens to the return value, casting it to Integer
and a ClassCastException
is thrown.
在get
方法内部,String.class
从类型映射中正确检索到,但对Class<Integer>
. 调用type.cast(...)
不会抛出,因为从数据映射中检索到的值是String
. 一个隐式检查转换然后实际发生在返回值上,将它转换为Integer
并ClassCastException
抛出 a 。
This strange interaction is due to type erasure.
这种奇怪的交互是由于类型擦除。
So, when we are storing multiple types in a single data structure, there are a number of ways to approach it, depending on what our needs are.
因此,当我们在单个数据结构中存储多种类型时,有多种方法可以处理它,具体取决于我们的需求。
1. We can forego compilation checks if there is no way to perform them.
1. 如果无法执行编译检查,我们可以放弃编译检查。
Storing the Class
is pointless for the most part here because, as I showed above, it doesn't perform a useful validation. So we could redesign the map along the following lines:
在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
and getImplicit
do a similar thing but:
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");
In both cases we're just relying on our own awareness as a programmer to not make mistakes.
在这两种情况下,我们都只是依靠自己作为程序员的意识来避免犯错。
Suppressing warnings isn't necessarily bad, it's just important to understand what they actually mean and what the implications are.
抑制警告不一定是坏事,重要的是要了解它们的实际含义以及含义。
2. Pass a Class
to get
so the return value must be valid.
2. 传递a Class
toget
所以返回值必须是有效的。
This is the way I've seen it done typically.
这是我看到的典型做法。
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);
But, of course, it means we need to pass two parameters to get
.
但是,当然,这意味着我们需要将两个参数传递给get
.
3. Parameterize the keys.
3. 参数化键。
This is a novel but more advanced and it may or may not be more convenient.
这是一种新颖但更先进的方法,它可能更方便,也可能不更方便。
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);
}
}
}
So e.g.:
所以例如:
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));
This might make sense if the entries are known or predictable so we can have something like:
如果条目是已知或可预测的,这可能是有意义的,因此我们可以有以下内容:
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);
}
Then we don't have to construct a key object any time a retrieval is done. This works very well especially for adding some strong typing to stringly-typed scenarios.
这样我们就不必在检索完成时构造一个关键对象。这非常有效,特别是对于向字符串类型的场景添加一些强类型。
Foo foo = example.get(Keys.FOO);
4. Don't have a map any kind of object can be put in, use polymorphism of some kind.
4. 没有地图可以放入任何类型的对象,使用某种多态。
When possible and not too cumbersome, it's a good option. If there is a common behavior that the different types are used for, make it an interface or superclass so we don't have to use casting.
如果可能并且不太麻烦,这是一个不错的选择。如果使用不同类型的共同行为,请将其设为接口或超类,这样我们就不必使用强制转换。
A simple example might be like this:
一个简单的例子可能是这样的:
// 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"));
And we could instead substitute something like this:
我们可以代替这样的东西:
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();
回答by Judge Mental
You are attempting to assign an element of type Class
to a variable of type Class<T>
. Of course that's an unchecked assignment. You seem to be implementing a heterogeneous map. Java (and any other strongly-typed language) has no way to express the value type of your map in a statically-type-safe way.
您正试图将 type 元素分配给 typeClass
变量Class<T>
。当然,这是一个未经检查的分配。您似乎正在实施异构地图。Java(和任何其他强类型语言)无法以静态类型安全的方式表达映射的值类型。
That is because the element types are only known at runtime. Expecting the compiler to statically type-check things that are not yet known is asking too much of it. The compiler can't even do reasonable static type inference, so expecting it to predict the future for dynamic type inference is really a stretch.
那是因为元素类型仅在运行时才知道。期望编译器对未知的事物进行静态类型检查,这要求太多了。编译器甚至无法进行合理的静态类型推断,因此期望它预测动态类型推断的未来确实是一种延伸。