В этом примере:
import java.util.*;
public class Example {
static void doesntCompile(Map<Integer, List<? extends Number>> map) {}
static <T extends Number> void compiles(Map<Integer, List<T>> map) {}
static void function(List<? extends Number> outer)
{
doesntCompile(new HashMap<Integer, List<Integer>>());
compiles(new HashMap<Integer, List<Integer>>());
}
}
doesntCompile()
не может скомпилировать с:
Example.java:9: error: incompatible types: HashMap<Integer,List<Integer>> cannot be converted to Map<Integer,List<? extends Number>>
doesntCompile(new HashMap<Integer, List<Integer>>());
^
пока compiles()
принимается компилятором.
Этот ответ объясняет, что единственное отличие состоит в том <? ...>
, что в отличие от этого , <T ...>
вы можете ссылаться на тип позже, что, похоже, не так.
В чем разница между <? extends Number>
и <T extends Number>
в этом случае, и почему первая компиляция не выполняется?
Ответы:
Определив метод со следующей подписью:
и вызывая его так:
В jls §8.1.2 мы находим, что (интересная часть выделена мной):
Другими словами, тип
T
сопоставляется с типом ввода и назначаетсяInteger
. Подпись эффективно станетstatic void compiles(Map<Integer, List<Integer>> map)
.Когда дело доходит до
doesntCompile
метода, jls определяет правила подтипирования ( §4.5.1 , выделенный мной):Это означает, что
? extends Number
действительно содержитInteger
или дажеList<? extends Number>
содержитList<Integer>
, но это не относится кMap<Integer, List<? extends Number>>
иMap<Integer, List<Integer>>
. Больше на эту тему можно найти в этой теме . Вы все еще можете заставить?
работать версию с подстановочными знаками, объявив, что вы ожидаете подтипList<? extends Number>
:источник
? extends Number
а не? extends Numeric
. [2] Ваше утверждение о том, что «это не относится к List <? Extends Number> и List <Integer>» , неверно. Как уже указывал @VinceEmigh, вы можете создать методstatic void demo(List<? extends Number> lst) { }
и вызывать его такdemo(new ArrayList<Integer>());
или иначеdemo(new ArrayList<Float>());
, и код компилируется и запускается нормально. Или я, возможно, неправильно понимаю или неправильно понимаю то, что вы заявили?List<? extends Number>
в виду как параметр типа всей карты, а не самой. Большое спасибо за комментарий.List<Number>
не содержитList<Integer>
. Предположим, у вас есть функцияstatic void check(List<Number> numbers) {}
. Когда вызов сcheck(new ArrayList<Integer>());
ним не компилируется, вы должны определить метод какstatic void check(List<? extends Number> numbers) {}
. С картой то же самое, но с большей вложенностью.Number
и параметр типа списка, и вы должны добавить его,? extends
чтобы сделать его ковариантным,List<? extends Number>
является параметром типаMap
и также нуждается? extends
в ковариации.В звонилке:
T соответствует Integer, поэтому тип аргумента a
Map<Integer,List<Integer>>
. Это не относится к методуdoesntCompile
: тип аргумента остаетсяMap<Integer, List<? extends Number>>
тем же фактическим аргументом в вызове; и это не присваивается изHashMap<Integer, List<Integer>>
.ОБНОВИТЬ
В
doesntCompile
методе ничто не мешает вам сделать что-то вроде этого:Очевидно, что он не может принять
HashMap<Integer, List<Integer>>
аргумент в качестве аргумента.источник
doesntCompile
, тогда? Просто интересно об этом.doesntCompile(new HashMap<Integer, List<? extends Number>>());
будет работать, как и раньшеdoesntCompile(new HashMap<>());
.HashMap<Integer, List<Integer>>
», не могли бы вы пояснить, почему это нельзя назначить из него?Упрощенный пример демонстрации. Тот же пример можно визуализировать как ниже.
List<Pair<? extends Number>>
является многоуровневым типом подстановочных знаков, тогда какList<? extends Number>
является стандартным типом подстановочных знаков.Допустимые конкретные экземпляры подстановочного типа
List<? extends Number>
включают в себяNumber
и любые подтипы,Number
тогда как в случае,List<Pair<? extends Number>>
когда это аргумент типа аргумента типа, а сам по себе имеет конкретный экземпляр универсального типа.Дженерики являются инвариантами, поэтому
Pair<? extends Number>
можно использовать только групповые символыPair<? extends Number>>
. Внутренний тип? extends Number
уже ковариантен. Вы должны сделать включающий тип ковариантным, чтобы разрешить ковариацию.источник
<Pair<Integer>>
не работает,<Pair<? extends Number>>
но работает<T extends Number> <Pair<T>>
?T
против?
. Часть проблемы заключается в том, что, когда Андроник достигает жизненно важной точки своего объяснения, он обращается к другой теме, которая использует только тривиальные примеры. Я надеялся получить более четкий и полный ответ здесь.Я бы порекомендовал вам ознакомиться с документацией об универсальных подстановочных знаках, особенно с инструкциями по их использованию.
Откровенно говоря ваш метод #doesntCompile
и звоните как
В корне неверно
Добавим юридическую реализацию:
Это действительно хорошо, потому что Double расширяет Number, так что put
List<Double>
тоже отличноList<Integer>
, верно?Тем не менее, вы все еще думаете, что это законно, чтобы перейти
new HashMap<Integer, List<Integer>>()
от вашего примера?Компилятор так не считает, и делает все возможное, чтобы избежать подобных ситуаций.
Попробуйте сделать ту же реализацию с методом #compile, и компилятор, очевидно, не позволит вам поместить список двойников в карту.
По сути, вы ничего не можете поместить, но
List<T>
поэтому безопасно вызывать этот метод с помощьюnew HashMap<Integer, List<Integer>>()
илиnew HashMap<Integer, List<Double>>()
илиnew HashMap<Integer, List<Long>>()
илиnew HashMap<Integer, List<Number>>()
.Итак, в двух словах, вы пытаетесь обмануть с помощью компилятора, и он справедливо защищает от такого обмана.
NB: ответ Мориса Перри абсолютно верный. Я просто не уверен, что это достаточно ясно, поэтому постарался (очень надеюсь, что мне удалось) добавить более обширный пост.
источник