Почему следующее приведение приводит к ошибке компиляции? - PullRequest
1 голос
/ 14 февраля 2012

У меня есть следующий код:

public static<F, S> void xyz() {
  class Pair<X, Y> {}    
  class Role<F> {}

  Map<?, List<Pair<?, ?>>> map = null;
  Role<F> role = null;

  List<Pair<F, S>> result = (List<Pair<F, S>>) map.get(role);
}

К сожалению, Java жалуется на приведение в последней строке.Почему это происходит?

В моем варианте это противоречит здравому смыслу.Приведение должно быть разрешено в любом месте, где есть возможность правильного соответствия типов.

Ответы [ 5 ]

1 голос
/ 14 февраля 2012

Обобщения не являются совершенно новой системой типов. Внутренне все это основано на приведениях (из-за обратной совместимости байтового кода).

Расширяя ответ Тома, давайте сделаем простейшие возможные примеры:

List<? extends Object> genericList; // List<?> is short for List<? extends Object>
List<String> concreteList = new LinkedList<String>();

genericList = concreteList;
concreteList = (List<String>) genericList; //(caution: uncheck cast)

Итак, это работает, так как String расширяет Object. Давайте пойдем дальше и сделаем списки списков:

List<List<?>> listOfGenericLists;
List<List<String>> listOfStrings = new LinkedList<List<String>>();
//does not work "only because" String extends Object:
listOfGenericLists = listOfStrings; //compile error

Это та же ошибка, что и в вашем примере. Интуиция заключается в том, чтобы сказать, что это должно работать, потому что String расширяет Object и, следовательно, List<String> должен расширять List<Object>. Ну, это не так. Вы, вероятно, не были бы удивлены этим: Список> listOfIntegers; listOfIntegers = listOfStrings; // также ошибка компиляции

Посмотрим, что произойдет, когда мы сделаем интуицию явной. Давайте продолжим из списков:

List<? extends List<?>> genericListOfLists = new LinkedList<List<?>>();
List<? extends List<String>> genericListOfStringLists = new LinkedList<List<String>>();
genericListOfLists = listOfStrings;
genericListOfStringLists = listOfStrings;
listOfStrings = (List<List<String>>) genericListOfLists; //(caution: unchecked cast)
listOfStrings = (List<List<String>>) genericListOfStringLists; // works as before (caution: uncheck cast)

Ах-ха! Теперь интуиция вернулась на путь. Правильно? На самом деле нет:

List<? extends List<Integer>> listOfIntegerLists;
listOfIntegerLists = (List<List<Integer>>) genericListOfLists; //(caution: uncheck cast)
listOfIntegerLists = (List<List<Integer>>) genericListOfStringLists; //OUCH (no compilation error just an unchecked cast!)

Причина в том, что, как было сказано ранее: с точки зрения типов все родовые типы одинаковы, и приведение типов выполняется на уровне типов.

Я могу порекомендовать Обобщения и коллекции Java Мориса Нафталина и Филиппа Уодлера (О'Рейли) для деталей.

1 голос
/ 14 февраля 2012

Состав не имеет никакого смысла.Это не наоборот расширяющего приведения.

Аналогичное возможное приведение, где у нас есть аргумент с подстановочными знаками:

List<String> ls;
List<?> cast = ls;

В этом случае я не могу впоследствии добавить Integer к объекту ls / cast.

cast.add(Integer.valueOf(0)); // Not going to work.

Если сам универсальный аргумент является универсальным с подстановочным знаком, ситуация совершенно иная.

List<List<String>> strses;
List<List<?>> cast = strses; // Suppose this actually worked.
List<Integer> ints;
List<?> wildInts = ints;
cast.add(0, wildInts); // Good.
List<String> wasInts = strses.get(0); // Oops!!
1 голос
/ 14 февраля 2012

Когда объект создан, он имеет точный тип. Этот тип может быть ArrayList<Pair<F, S>>, который, конечно, является подтипом List<Pair<F, S>>. Однако, как указал Том, это не подтип List<Pair<?,?>>. Таким образом, ваш актерский состав будет правильным только в том случае, если где-то еще есть неправильный актерский состав. Если в какой-то момент дженерики были реализованы, то ваш каст или этот неправильный каст сломался бы. Это обоснование того, почему этот бросок отклонен.

0 голосов
/ 14 февраля 2012

«Приведение должно быть разрешено в любом месте, где есть возможность правильного соответствия типов». В этом случае НЕТ возможности правильного соответствия типов.

Объект никогда не может быть экземпляром как List<A>, так и List<B>, если A и B являются конкретными, а не одинаковыми, даже если A является подтипом B или чем-то еще. Например, у вас не может быть объекта, который является List<String> и List<Object>. Существует (концептуально) определенный параметр типа для каждого объекта. Вы должны сначала понять эту часть.

Ваш пример точно такой же - List<Pair<F, S>> и List<Pair<?, ?>>. По той же причине, что и в случае String и Object, объект типа List<Pair<?, ?>> никогда не может быть List<Pair<F, S>>.

.

Обновление: дополнительные пояснения на случай, если это поможет. @Konstantin: Если вы напишите Pair<?, ?>, то параметры типа на верхнем уровне, которые являются ? (подстановочные знаки), являются гибкими. Pair<T1, T2> совместим с Pair<?, ?>. Однако в List<Pair<?, ?>>, ? не на верхнем уровне. Параметр типа на верхнем уровне - Pair<?, ?>, который не является подстановочным знаком и поэтому не является гибким. Неважно, что у вас есть ? глубже вниз. Если у вас есть List<? extends Pair<?, ?>>, то верхний уровень является групповым символом и является гибким.

То, что вы, вероятно, хотите, это List<? extends Pair<?, ?>>. Возможно, это поможет вам рассмотреть разницу между List<Pair<?, ?>> и List<? extends Pair<?, ?>>. List<Pair<?, ?>> говорит, что это список, и его тип параметра точно тип Pair<?, ?> (и ничего больше, не Pair<T1, T2> и т. Д.). List<? extends Pair<?, ?>> говорит, что это List, а параметр его типа является подтипом Pair<?, ?> (и включает в себя Pair<T1, T2>).

0 голосов
/ 14 февраля 2012

Вы хотите привести общий экземпляр списка к другому универсальному экземпляру списка:

List<Pair<?, ?>> ==>> List<Pair<F, S>>

Вы не можете решить эту проблему следующим образом:

List<Pair<?, ?>> result = (List<Pair<?, ?>>) map.get(role);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...