Страницы

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

пятница, 31 января 2020 г.

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

#java #generics


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
ограничивает самой коллекцией и ее родителями?
    


Ответы

Ответ 1



Забудем пока про конкретно ваш код и рассмотрим более простой пример. Допустим, есть вот такие классы: // Для демонстрации иерархии типов 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.

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

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