简体   繁体   English

如何在java Map接口中关联泛型类型参数的值?

[英]how can I relate values of generic type parameters in java Map interface?

I don't think the title of the question will be clear, but the idea is simple. 我不认为问题的标题会很清楚,但这个想法很简单。

Suppose I have a Map type variable. 假设我有一个Map类型变量。

Map<K,V> myMap;

but I want to establish a relation between K and V. for example, I'd like to say that this Map relates Sets of some class to objets of that class. 但是我想建立一个K和V之间的关系,例如,我想说这个Map将某些类的集合与该类的objets相关联。 Something like: 就像是:

Map<Set<T>, T> myMap;

but not for a specific type T. I'd like this Map to accept entries like 但不是特定类型T.我希望这个地图接受像这样的条目

(Set<String>, String),
(Set<Integer>, Integer)
...

Is there a possible declaration for myMap that allows me to have this behavior? 是否有可能的myMap声明允许我有这种行为? Please let me know if I'm explaining myself wrongly or if I have a previous conceptual error. 如果我错误地解释自己或者我之前有过概念错误,请告诉我。

Sadly this is not possible with Java generics. 遗憾的是,Java泛型不可能实现这一点。 If Java allowed higher order type parameters, then one could have defined Map something like: 如果Java允许更高阶的类型参数,那么可以将Map定义为:

public interface Map<V<>> {  // here V<> is my hypothetical syntax for a 
                             // type parameter which is itself generic...
    <K>
    V<K> put(K key, V<K> value);
    ...
}

instead of the actual java.util.Map : 而不是实际的java.util.Map

public interface Map<K, V> {
    V put(K key, V value);
    ...
}

You can see that the problem is that K is declared once for the whole class and not for each call to .put() . 您可以看到问题是K为整个类声明了一次,而不是每次调用.put()

Enough fantasizing, so what can you do? 足够的幻想,你能做什么? I think the best is to create a Map<Set<?>, Object> and wrap it as a private member. 我认为最好是创建一个Map<Set<?>, Object>并将其包装为私有成员。 Then you are free to create your own put() and get() which take into account the intended "relation" between types: 然后你可以自由创建自己的put()get() ,它们考虑了类型之间的预期“关系”:

class SpecialMap {
    private Map<Set<?>, Object> map = ...;

    public <T>
    T put(Set<T> key, T value) {
        return (T) map.put(key, value);
    }

    public <T>
    T get(Set<T> key) {
        return (T) map.get(key);
    }
}

What you are trying to do doesn't seem like a good diea, because each Set<T> is always not equal to another Set<T> even if of the same type - using Sets as keys is more or less useless. 你想要做的事情似乎不是一个好的迪亚,因为每个Set<T>总是不等于另一个Set<T>即使是相同的类型 - 使用Sets作为键或多或少是无用的。

That said, you don't need to define a new class - you can require a method to accept such a map: 也就是说,您不需要定义新类 - 您可以要求一个方法来接受这样的地图:

public static <T> void process(Map<Set<T>, T> map) {
    for (Map.Entry<Set<T>, T> entry : map) {
        Set<T> key = entry.getKey();
        T value = entry.getValue();
        // do something
    }
}

There is no way with generics to have the compiler verify a different T for each put() call. 对于每个put()调用,编译器无法使用泛型验证不同的T In other words, you can't have the same map and do: 换句话说,你不能拥有相同的地图并做:

myMap.put(new HashSet<String>(), "foo");
myMap.put(new HashSet<Integer>(), 1);

If you need this then you may have to store <Object> and do the verification yourself using instanceof or some other hack. 如果你需要这个,那么你可能必须存储<Object>并使用instanceof或其他一些hack自己进行验证。

Now, you can definitely do something like: 现在,你绝对可以这样做:

public class MyMap<T> extends HashMap<Set<T>, T> {
    ...

Then you can do: 然后你可以这样做:

MyMap<String> myMap = new MyMap<String>();
Set<String> set = new HashSet<String>();
myMap.put(set, "foo");

Remember that the key has to have a valid hashCode() and equals() methods which might be expensive with a Set . 请记住,密钥必须具有有效的hashCode()equals()方法,这些方法对于Set可能很昂贵。

I don't think it's possible to achieve compile-time checking with Java generics. 我认为用Java泛型实现编译时检查是不可能的。 However it's quite simple at runtime. 但是它在运行时非常简单。 Just right a short decorator: 恰好是一个简短的装饰者:

public class FancyTypeMapDecorator implements Map<Set<? extends Object>, Object> {

    final Map<Set<? extends Object>, Object> target;

    public FancyTypeMapDecorator(Map<Set<? extends Object>, Object> target) {
        this.target = target;
    }

    @Override
    public Object put(Set<? extends Object> key, Object value) {
        final Class<?> keyElementType = key.iterator().next().getClass();
        final Class<?> valueType = value.getClass();
        if (keyElementType != valueType) {
            throw new IllegalArgumentException(
                "Key element type " + keyElementType + " does not match " + valueType);
        }
        return target.put(key, value);
    }

    @Override
    public void putAll(Map<? extends Set<? extends Object>, ? extends Object> m) {
        for (Entry<? extends Set<? extends Object>, ? extends Object> entry : m.entrySet()) {
            put(entry.getKey(), entry.getValue());
        }
    }

    //remaining methods are simply delegating to target

}

Here's how it works: 以下是它的工作原理:

final Map<Set<? extends Object>, Object> map =
    new FancyTypeMapDecorator(new HashMap<Set<? extends Object>, Object>());

Set<? extends Object> keyA = Collections.singleton(7);
map.put(keyA, 42);

Set<? extends Object> keyB = Collections.singleton("bogus");
map.put(keyB, 43);

Second put throws an exception. 第二put抛出异常。

However both the implementation (and I don't even mean that it will fail for empty Set as a key) and usage/API triggers an alarm bell... Do you really want to deal with such a structure? 然而这两个实现(我甚至不认为它会因为空Set作为键而失败)和usage / API触发警钟......你真的想要处理这样的结构吗? Maybe you need to rethink your problem? 也许你需要重新思考你的问题? What are you actually trying to achieve? 究竟想要实现什么目标?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM