Страницы

Поиск по вопросам

вторник, 2 апреля 2019 г.

Дженерики, ошибка “Wrong 2nd argument type”

public static boolean addToGroupMap(K key, V value, Map> checkMap){
assert checkMap!=null; boolean result = false;
Collection vList = checkMap.get(key); if (vList==null){ checkMap.put(key, new ArrayList(Collections.singleton(value))); }else { vList.add(value); result = true; } return result; }
Думал, что более менее разобрался с дженериками, однако, в упор не понимаю, почему в строке: checkMap.put(key, new ArrayList(Collections.singleton(value))); Компилятор выдает ошибку:
Wrong 2nd argument type. Found: 'java.util.ArrayList', required: '? extends java.util.Collection
Сначала в сигнатуре метода было : K key, V value, Map> checkMap. Однако теперь надо, чтобы в значении мапы была именно коллекция. При этом, нет желания менять в куче кода List<> в checkMap'e на Collection<>, да и почему это надо делать, если лист имплементирует коллекцию?
UDP: Также, возникает вопрос. Почему, если заменить в сигнатуре ? extends Collection на ? super Collection, пропадает доступ к методам коллекции? Ведь вроде ? super Collection ограничивает самой коллекцией и ее родителями?


Ответ

Забудем пока про конкретно ваш код и рассмотрим более простой пример. Допустим, есть вот такие классы:
// Для демонстрации иерархии типов class A { } class B extends A { } class C extends B { }
// Для демонстрации контейнера class S { private V value;
public V get() { return value; } public void set(V value) { this.value = value; } }
Рассмотрим такой контейнер, как S. Каким будет возвращаемое значение для метода get? Этот метод вернет что-то, что является наследником B. То есть, что бы метод get ни вернул - это что-то можно записать в переменную типа B.
S s; B v = s.get(); // Значение типа ? extends B всегда можно записать в переменную типа B
Теперь рассмотрим метод set. Кажется, что все нормально? Но давайте сделаем вот так:
S s = new S(); s.set(new B()); // Ошибка - значение типа B не может быть передано как параметр типа C
Здесь я создал конкретный контейнер для примера. Даже если на самом деле используется S - компилятор должен обеспечить корректность кода в любом случае.
Теперь рассмотрим такой контейнер как S. Метод set у него можно вызвать с параметром типа B
S s; s.set(new B()); // Значение типа B всегда можно передать как ? super B
А вот метод get нормально вызвать не получится:
S s = new S(); B v = s.get(); // Ошибка - попытались значение типа A записать в переменную типа B
В итоге получается, что get-методы требуют отношения extends, а set-методы требуют отношения super. Если вашему коду нужно использовать оба типа методов - придется определять оба отношения одновременно, т.е. оставить просто

Возвращаюсь к вашему коду, можно заметить, что из параметра checkMap вы одновременно получаете данные (get) - и передаете их ему (put). Поэтому, вы не можете использовать ? extends ... в определении параметра. Единственный способ сделать оба вызова рабочими - использовать конкретный тип данных. Например, Map>
Если же вам нужно работать с разными отображениями - то сам тип коллекции надо делать обобщенным: Map где C extends Collection. Для того чтобы создать такую коллекцию, вам нужно будет принимать фабрику или класс как параметр:
public static > boolean addToGroupMap(K key, V value, Map checkMap, Callable collectionFactory) { C vList = checkMap.get(key); checkMap.put(key, vList = collectionFactory.call()); }
public static > boolean addToGroupMap(K key, V value, Map checkMap, Class collectionClass) { C vList = checkMap.get(key); checkMap.put(key, vList = collectionClass.newInstance()); }
Смысл фабрики - в том, что ее создает тот код, который знает точный тип коллекции:
Map> checkMap1 = new Map<>(); Callable> factory1 = () -> new ArrayList();
Map> checkMap2 = new Map<>(); Callable> factory2 = () -> new HashSet();
Дальше фабрика по цепочке вызовов передается пока не придет в метод addToGroupMap

Комментариев нет:

Отправить комментарий