管理集合映射的通用方法

时间:2012-03-18 16:10:52

标签: java generics collections

在我正在开发的应用程序中,我使用了几个将字符串与元素集合相关联的映射,例如Map<String, List<String>>Map<String, SortedSet<Object>>。在许多情况下,我想要简单的函数来添加/删除由某个键给出的集合中的元素,可能会在地图中删除或创建新条目。

我为效果实现了一些通用方法,但putIntoCollection()方法给了我一些问题。我的实施没有引起任何警告,如下:

public static <K, V, C extends Collection<V>> void putIntoCollection(
        Map<K, C> map, K key, V value, Class<? extends C> collectionClass)
        throws InstantiationException, IllegalAccessException {
    C collection = map.get(key);
    if (collection == null) {
        collection = collectionClass.newInstance();
        map.put(key, collection);
    }
    collection.add(value);
}

C表示集合的类型,可以是任何类型的CollectionClass<? extends C>参数允许我传递具体的类令牌来实例化新的C }(例如,为ArrayList的地图传递Lists的标记。

但是,如果我尝试这样使用它:

Map<String, Set<String>> tags;
String key, value;
MapUtilities.putIntoCollection(map, key, value, HashSet.class);

我收到编译错误:

The parameterized method <K, V, Set<V>>putIntoCollection(Map<K,Set<V>>, K, V, Class<? extends Set<V>>) of type MapUtilities is not applicable for the arguments (Map<K,Set<V>>, K, V, Class<HashSet>) 

我理解这是因为我在传递一个Class<HashSet>参数,而它需要一个参数化的Set类。但是,我不知道如何(或者是否)可以获得Class的此类实例。有没有更好的方法来做这样的泛型方法?

4 个答案:

答案 0 :(得分:2)

您是否可以使用第三方库?您基本上正在重新发明Guava's Multimap - ListMultimap<String, String>SortedSetMultimap<String, Object>是您的两个例子。提供了大量实现 - 最值得注意的是,ArrayListMultimapTreeMultimap

也就是说,传递一个显式工厂对象通常更容易:

interface Supplier<T> { 
  T get();
}

void putIntoCollection(Map<K, Set<V>>, K, V, Supplier<Set<V>> emptySetSupplier);

答案 1 :(得分:1)

这是一个与泛型类型相关的问题...我认为Guice使用TypeLiteral来解决这个问题。

但我会回避你的问题。看起来你真正想要的是番石榴的Multimap。它是一个类似于Map的集合,但可以将多个值与一个键相关联。它还提供实用方法,例如您正在寻找的方法。

您可以在番石榴维基中找到详细说明:http://code.google.com/p/guava-libraries/wiki/NewCollectionTypesExplained#Multimap

答案 2 :(得分:1)

在尝试使用泛型之后,这是我能找到的最好的(工作!)解决方案:

@SuppressWarnings("unchecked")
public static <K, V,  C extends Collection<V>> void putIntoCollection(
    Map<K, C> map, K key, V value, Class<?> collectionClass)
throws InstantiationException, IllegalAccessException {

    C collection = map.get(key);
    if (collection == null) {
        collection = (C) collectionClass.newInstance();
        map.put(key, collection);
    }
    collection.add(value);

}

unchecked警告是不可避免的,Class<?>参数和(C)演员也是如此。使用此方法遇到的麻烦归结为类型擦除 - 在运行时,您无法在映射中准确指定集合的​​泛型类型,因为此信息仅在编译时存在,在程序执行期间丢失。

现在这可以顺利运行:

Map<String, Set<String>> map = new HashMap<String, Set<String>>();
String key="x", value="y";
putIntoCollection(map, key, value, HashSet.class);

请注意,这也可以在没有编译错误的情况下运行:

putIntoCollection(map, key, value, Vector.class);

在当前形式中,无法在方法中指定地图中的集合值(类型Set<String>)与方法中实例化的集合值具有相同的类型({{1}在第一个例子中,这是正确的,在第二个例子中是HashSet,这是不正确的)。再次,这是因为类型擦除,在编译时两个集合实例都工作正常(VectorHashSet),因为它们都实现Vector并包含Collection类型的元素,但是在运行时,此行将适用于第一个示例,但对于带有String的第二个示例将失败:

ClassCastException

答案 3 :(得分:0)

您的方法无效,因为Class<List>不是Class<Collection>的子类。因此,您将无法使用Class的实例或任何其他带有感兴趣类型(? extends C)的容器作为泛型类型。您只能使用C作为直接参数,就像一种原型。

<T> T[] java.util.List.toArray(T[] a)为例。此方法也只是想知道结果数组的类型T。它无法作为Class<T>传递,因此该方法希望客户端提供实例。