Общий метод для управления картой коллекций - PullRequest
3 голосов
/ 18 марта 2012

В разрабатываемом приложении я использую несколько карт, связывающих строки с коллекциями элементов, например, 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.Есть ли лучший способ сделать общий метод, такой как этот?

Ответы [ 5 ]

2 голосов
/ 18 марта 2012

Вы в состоянии использовать сторонние библиотеки?Вы в основном заново изобретаете 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 голос
/ 18 марта 2012

Немного поигравшись с генериками, это лучшее (работающее!) Решение, которое я смог найти:

@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>) имеют тот же тип, что и значения коллекции, созданные в методе (HashSet в первом примере, что является правильным и Vector ввторой пример, который неверен).Опять же, это из-за стирания типа, во время компиляции оба экземпляра коллекции работают нормально (HashSet и Vector), потому что оба реализуют Collection и содержат элементы типа String, но во время выполнения эта строка будет работать дляпервый пример, но не удастся для второго примера с ClassCastException:

Set<String> set = map.get(key);
1 голос
/ 18 марта 2012

Это проблема, связанная с универсализацией типов ... Я думаю, что Guice использует TypeLiteral , чтобы обойти эту проблему.

Но я собираюсь обойти ваш вопрос. Кажется, что вы действительно хотите, это Multimap от Guava. Эта коллекция похожа на карту, но может связывать несколько значений с одним ключом. Он также предлагает полезные методы, такие как тот, который вы искали.

Вы можете найти подробное объяснение в вики Guava: http://code.google.com/p/guava-libraries/wiki/NewCollectionTypesExplained#Multimap

0 голосов
/ 18 марта 2012

Ваш подход не может работать, потому что Class<List> не является подклассом Class<Collection>. Таким образом, вы не сможете использовать экземпляр Class или любой другой контейнер, который несет интересующий тип (? extends C) в качестве универсального типа. Вы можете использовать C в качестве прямого параметра, например, в качестве прототипа.

Посмотрите на <T> T[] java.util.List.toArray(T[] a) в качестве примера. Этот метод также просто хочет знать тип T результирующего массива. Он не может быть доставлен как Class<T>, поэтому метод хочет, чтобы клиент дал экземпляр.

0 голосов
/ 18 марта 2012

Мне кажется, это слишком перегружено. Рассматривали ли вы вместо Guava Multimaps ?

http://code.google.com/p/guava-libraries/wiki/CollectionUtilitiesExplained#Multimaps

...