Несовместимые типы при использовании верхнего ограничивающего подстановочного знака



Я действительно запутался в том, как работают верхние ограниченные типы в генераторах Java.



Допустим, у меня есть



interface IModel<T>
interface I
class A implements I
class B implements I
class C implements I


Тогда у меня есть метод с параметром следующим образом



foo(IModel<Map<? extends I, Map<? extends I, List<? extends I>>>> dataModel)


Вызов этого метода как



IModel<Map<A, Map<B, List<C>>>> model = ...
foo(model)


Заканчивается ошибкой компиляции



Error:(112, 49) java: incompatible types: IModel<java.util.Map<A,java.util.Map<B,java.util.List<C>>>> cannot be converted to IModel<java.util.Map<? extends I,java.util.Map<? extends I,java.util.List<? extends I>>>>


Я читал документы о Java generics из Oracle web, пытаясь найти их в google, но там должно быть что-то, что я совершенно неправильно понял.
559   3  

3 ответов:

Этот вопрос можно закоротить как почему

foo(IModel<List<? extends I>> dataModel)

Не может принять аргумент, подобный

IModel<List<A>> model

Объяснение

List<A> является подтипом List<? extends I>, так что все в порядке:

public void bar(List<? extends I> list);
List<A> listA;
bar(listA);

Но, это не делает IModel<List<A>> подтипом IModel<List<? extends I>>, так же, как IModel<Dog> не является подтипом IModel<Animal>, Таким образом, код, который вы разместили, не может быть скомпилирован.

Решение

Вы можете изменить его на:

foo(IModel<? extends Map<? extends I, ? extends Map<? extends I, ? extends List<? extends I>>>> dataModel)

Или

<FIRST extends I, SECOND extends I, THIRD extends I> void foo(IModel<Map<FIRST, Map<SECOND, List<THIRD>>>> dataModel)

Чтобы заставить его компилироваться.

Прежде всего, мне интересно, сколько усилий потребовалось бы вам (одному человеку), чтобы разобраться в коде, который должен быть в такой форме:

import java.util.List;
import java.util.Map;

interface IModel<T> {}
interface I {}
class A implements I {}
class B implements I {}
class C implements I {}

public class UpperBounds
{
    public static void main(String[] args)
    {
        IModel<Map<A, Map<B, List<C>>>> model = null;
        foo(model);
    }

    static void foo(IModel<Map<? extends I, Map<? extends I, List<? extends I>>>> dataModel)
    {

    }
}

Вместо того, чтобы позволить сотням людей (которые хотят помочь вам) сделать это самостоятельно, чтобы иметь что-то, что они могут скомпилировать и посмотреть в своей IDE. Я имею в виду, что это не так уж сложно.


Как говорится: технически, вы пропускаете здесь еще несколько extends пунктов. Это компилирует отлично:

import java.util.List;
import java.util.Map;

interface IModel<T> {}
interface I {}
class A implements I {}
class B implements I {}
class C implements I {}

public class UpperBounds
{
    public static void main(String[] args)
    {
        IModel<Map<A, Map<B, List<C>>>> model = null;
        foo(model);
    }

    static void foo(IModel<? extends Map<? extends I, ? extends Map<? extends I, ? extends List<? extends I>>>> dataModel)
    {

    }
}

Но вы должны

Не

Реализуйте это так. Это непонятно. Каким бы ни был этот параметр dataModel, Вы должны рассмотреть возможность создания надлежащей структуры данных для этого, а не передавать такой беспорядок глубоко вложенных универсальных карт.


Причина, по которой исходная версия не компилировалась, уже упоминалась в других ответах. И это можно сделать более ясным, показав пример с использованием намного более простого вызова метода. Считать этот пример:
interface IModel<T> {}
interface I {}
class A implements I {}
class B implements I {}
class C implements I {}

public class UpperBounds
{
    public static void main(String[] args)
    {
        List<List<A>> lists = null;
        exampleA(lists); // Error
        exampleB(lists); // Works!
    }

    static void exampleA(List<List<? extends I>> lists)
    {

    }
    static void exampleB(List<? extends List<? extends I>> lists)
    {

    }
}

Метод exampleA не может принять данный список, тогда как метод exampleB может принять его.

Детали хорошо объяснены в какие отношения супер-подтипов существуют среди экземпляров общих типов? из часто задаваемых вопросов по дженерикам Анжелики Лангер.

Интуитивно ключевым моментом является то, что тип List<A> является подтипом List<? extends I>. Но разрешение методу принимать только a List<List<? extends I>> не позволяет передавать список, элементы которого являются подтипами из List<? extends I>. Чтобы принять подтипы, вы должны использовать ? extends.

(Это можно было бы даже упростить дальше: когда метод принимает List<Number>, то вы не можете передать в List<Integer>. Но это не прояснило бы здесь смысл List<A> быть подтипом List<? extends I>)

Имея метод method1(Map<I> aMap>) и будучи классом, реализующим I, вы не можете вызвать метод с помощью Map<A>, и это не случайно.

Имея метод:

public static void foo2(IModel<I> dataModel) {
        System.out.println("Fooing 2");
    }

Представьте себе такой код:

IModel<A> simpleModel = new IModel<A>() {};
foo2(simpleModel);

Это не должно работать, потому что вы предоставляете более конкретный тип методу, который требует универсального типа. Теперь представьте, что foo2 делает следующее:

   public static void foo2(IModel<I> dataModel) {
        dataModel = new IModel<B>() {};
        System.out.println("Fooing 2 after we change the instance");
    }

Здесь вы попытаетесь установить IModel на IModel, который является допустимым-потому что B расширяет I, но если бы вы могли вызвать это метод с IModel это не будет работать

Создайте свою модель следующим образом:

IModel<Map<I, Map<I, List<I>>>> model = ...

И в соответствующие карты и списки добавить объекты типа A, B и C, которые будут действительны, а затем вызвать функцию foo (model)

Comments

    Ничего не найдено.