避免在具有多个值类型的地图中取消选中分配? [英] Avoid unchecked assignment in a map with multiple value types?

查看:93
本文介绍了避免在具有多个值类型的地图中取消选中分配?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在Java 7中遇到了警告:

 未检查的赋值:'java.lang.Class' 'java.lang.Class< T>'

$ c> Class< T>



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

  private Map< String,Object> dataMap = new HashMap<>(); 
private Map< String,Class> typeMap = new HashMap<>();

public< T&实例){
dataMap.put(key,instance);
if(instance == null){
typeMap.put(key,null);
}
else {
typeMap.put(key,instance.getClass());
}
}

public< T& b $ b Class< T> type = typeMap.get(key);
if(type == null){
return null;
}
return type.cast .get(key));
}

它运行正常,但警告是有什么办法让Java做这个演员没有抱怨(除了抑制它)?或者有更好的方式来完成我想要做的吗?



谢谢!

解决方案

您显示的原因是不安全的,因为使用此作业:

 类< T> type = typeMap.get(key); 

T 使用从地图检索的 Class T 总是从调用 get 的周围上下文推断。例如,我可以做这个调用序列:

  // T从参数推断为String b $ b example.put(k,v); 
// T从返回值目标类型推断为Integer
Integer i = example.get(k);

get $ c> String.class 从类型映射正确检索,但未经检查的转换为 Class< Integer> 。调用 type.cast(...)不会抛出,因为从数据映射检索的值是 String 。然后,实际 发生在返回值之后,转换为 Integer > ClassCastException 被抛出。



这种奇怪的互动是由于 type erasure






多个类型在单个数据结构中,有多种方法来处理它,这取决于我们的需要。



1。



存储对于最常用的方法是无意义的部分在这里,因为,如上所示,它不执行有用的验证。因此,我们可以沿着以下行重新设计地图:

 类示例{
private final Map
void put(String k,Object v){
m.put(k,v);
}

对象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); 
//通用版本允许进行隐式转换
//(这本质上是你已经做的)
String b = example.getImplicit(k);

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

抑制警告并不一定不好,重要的是要了解它们的含义和意义。



< h3> 2。将类传递给 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);
//返回v
String s = example.get(k,String.class);
//返回null
Double d = example.get(k,Double.class);

但是,这意味着我们需要传递两个参数到 get



3参数化键。



这是一本小说,但更先进,可能也可能不方便。

  class Example {
private final Map< Key<?> m = new HashMap<>();

< V>键< 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< ;?延伸V> C;

@SuppressWarnings(unchecked)
Key(String k,V v){
//这个转换总是安全的,除非
//外界正在做某事鱼腥
//喜欢使用原始类型
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); 
//返回1.0f
Float f = example.get(k);
//返回null
Double d = example.get(new Key<>(k,Double.class));

这可能是有意义的,如果条目是已知或可预测的,所以我们可以有像:

  final class Key {
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 ); 



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



如果可能,不太麻烦,这是一个很好的选择。如果有不同类型使用的共同行为,使其成为接口或超类,所以我们不必使用转换。



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

  //一堆东西
Map< String,Object> map = ...;

//存储一些数据
map.put(abc,123L);
map.put(def,456D);

//等待一会
awhile();

//一段时间后,使用数据
//特别关于类型
consumeLong((Long)map.remove(abc));
consumeDouble((Double)map.remove(def));

而我们可以替换为这样:

  Map< String,Runnable> map = ...; 

//存储操作以及数据
//我们知道什么类型是
map.put(abc,() - > consumeLong(123L) );
map.put(def,() - > consumeDouble(456D));

awhile();

//消费,但是不再关心类型
map.remove(abc)。run();
map.remove(def)。run();


I'm having trouble with a warning in 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.

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:

    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?

Thanks!

解决方案

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 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 actually happens to the return value, casting it to Integer and a ClassCastException is thrown.

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.

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 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:

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.

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.

3. Parameterize the keys.

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.

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

这篇关于避免在具有多个值类型的地图中取消选中分配?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆