General method of managing the collection map

In an application I am developing, I am using multiple maps linking strings to sets of items, eg. Map<String, List<String>>

, Map<String, SortedSet<Object>>

. In many cases, I want simple functions to add / remove items to a collection given a specific key, possibly deleting or creating new entries in the map.

I have implemented some general methods for the effect, but the method is putIntoCollection()

giving me some problems. My implementation that doesn't raise any warnings is as follows:

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

represents a collection type, which can be any type Collection

, and the parameter Class<? extends C>

allows you to pass a specific class token to create a new one C

(for example, pass a token ArrayList

for a map Lists

).

However, if I try to use it like this:

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

      

I am getting compilation error:

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

      

I understand this is happening because I am passing the argument Class<HashSet>

while it is waiting for the parameterized Set class. However, I don't know how (or if) I can get such instances Class

. Is there a better way to do a general method like this?

+3


source to share


5 answers


Can you use third party libraries? You're basically reinventing Guava Multimap

- ListMultimap<String, String>

and SortedSetMultimap<String, Object>

are your two examples. Many implementations are provided - primarily for your case, ArrayListMultimap

and TreeMultimap

.

However, the simplest is to just pass an explicit factory object:



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

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

      

+2


source


This is a generic type reification issue ... I think Guice is using TypeLiteral to get around this issue.

But I'm going to get around your question. What you really want seems to be the Guava Multimap . It is a collection, similar to a map, but which can associate multiple values ​​with a single key. It also offers helpful methods like the one you were looking for.



You can find a detailed explanation on the Guava wiki: http://code.google.com/p/guava-libraries/wiki/NewCollectionTypesExplained#Multimap

+1


source


After using generics a little, this is the best (working!) Solution I could find:

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

}

      

The warning is unchecked

inevitable, as is the parameter Class<?>

and (C)

. The problem you run into with this method comes down to type erasure - at runtime, you cannot pinpoint the generic type of collections on the map, since this information only exists at compile time and is lost during program execution.

Now this will work without issue:

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

      

Remember this will also work without compilation errors:

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

      

In its current form, there is no way to indicate in a method that the collection values ​​on the map (of type Set<String>

) are of the same type as the collection values ​​created inside the method ( HashSet

in the first example, which is correct, and Vector

in the second example, which is wrong). Again, that due to type erasure at compile time, both collection instances work fine (a HashSet

and a Vector

) because both implement Collection

and contain elements of the type String

, but at run time this line will work for the first example, but won't be shown for the second example with ClassCastException

:

Set<String> set = map.get(key);

      

+1


source


0


source


Your approach may not work because it is Class<List>

not a subclass Class<Collection>

. This way you won't be able to use an instance Class

or any other container that carries the type of interest ( ? extends C

) as a generic type. You can use it C

as a direct parameter, as a kind of prototype.

Take a look at <T> T[] java.util.List.toArray(T[] a)

for an example. This method also just wants to know the type of the T

resulting array. It cannot be delivered as Class<T>

, so the method wants the client to provide an instance.

0


source







All Articles