在我正在开发的应用程序中,我使用了几个将字符串与元素集合相关联的映射,例如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
表示集合的类型,可以是任何类型的Collection
,Class<? 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
的此类实例。有没有更好的方法来做这样的泛型方法?
答案 0 :(得分:2)
您是否可以使用第三方库?您基本上正在重新发明Guava's Multimap
- ListMultimap<String, String>
和SortedSetMultimap<String, Object>
是您的两个例子。提供了大量实现 - 最值得注意的是,ArrayListMultimap
和TreeMultimap
。
也就是说,传递一个显式工厂对象通常更容易:
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
,这是不正确的)。再次,这是因为类型擦除,在编译时两个集合实例都工作正常(Vector
和HashSet
),因为它们都实现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>
传递,因此该方法希望客户端提供实例。