Java 8 Streams: почему Collectors.toMap ведет себя по-разному для дженериков с помощью подстановочных знаков?
Предположим, что у вас есть List
чисел. Значения в List
могут быть типа Integer
, Double
и т.д. Когда вы объявляете такой List
, его можно объявить с помощью шаблона (?
) или без подстановочного знака.
final List<Number> numberList = Arrays.asList(1, 2, 3D);
final List<? extends Number> wildcardList = Arrays.asList(1, 2, 3D);
Итак, теперь я хочу stream
использовать List
и collect
все > с помощью Collectors.toMap
(очевидно, приведенный ниже код является лишь примером для иллюстрации проблемы). Давайте начнем с потоковой передачи numberList
:
final List<Number> numberList = Arrays.asList(1, 2, 3D, 4D);
numberList.stream().collect(Collectors.toMap(
// Here I can invoke "number.intValue()" - the object ("number") is treated as a Number
number -> Integer.valueOf(number.intValue()),
number -> number));
Но я не могу выполнить ту же операцию на wildcardList
:
final List<? extends Number> wildCardList = Arrays.asList(1, 2, 3D);
wildCardList.stream().collect(Collectors.toMap(
// Why is "number" treated as an Object and not a Number?
number -> Integer.valueOf(number.intValue()),
number -> number));
Компилятор жалуется на вызов number.intValue()
со следующим сообщением:
Test.java: невозможно найти символ
symbol: метод intValue()
location: переменное число типа java.lang.Object
Из ошибки компилятора очевидно, что number
в лямбда рассматривается как Object
вместо number
.
Итак, теперь на мой вопрос (ы):
- При сборе подстановочной версии
List
, почему она не работает как несимвольная версия List
?
- Почему переменная
number
в лямбда считается Object
вместо number
?
Ответы
Ответ 1
Это тип вывода, который не понимает это правильно. Если вы явно указываете аргумент типа, он работает как ожидалось:
List<? extends Number> wildCardList = Arrays.asList(1, 2, 3D);
wildCardList.stream().collect(Collectors.<Number, Integer, Number>toMap(
number -> Integer.valueOf(number.intValue()),
number -> number));
Это известная ошибка javac: Вывод не должен отображать переменные захвата в их верхние границы. Статус, по словам Маурицио Чимадамора,
было предпринято исправление, после чего оно было отменено, поскольку оно прерывало случаи в 8, поэтому мы пошли на более консервативное исправление в 8, выполняя всю вещь в 9
По-видимому, исправление еще не было нажато. (Благодаря Joel Borggrén-Franck для указания меня в правильном направлении.)
Ответ 2
Объявление формы List<? extends Number> wildcardList
подразумевает "список с неизвестным типом, который является Number
или подклассом Number
". Интересно, что тот же самый список с неизвестным типом работает, если неизвестный тип ссылается на имя:
static <N extends Number> void doTheThingWithoutWildCards(List<N> numberList) {
numberList.stream().collect(Collectors.toMap(
// Here I can invoke "number.intValue()" - the object is treated as a Number
number -> number.intValue(),
number -> number));
}
Здесь N
по-прежнему остается неизвестным типом Number
или подклассом Number
", но вы можете обработать List<N>
по назначению. Вы можете присваивать List<? extends Number>
a List<N>
без проблем как ограничение, с которым неизвестный тип extends Number
совместим.
final List<? extends Number> wildCardList = Arrays.asList(1, 2, 3D);
doTheThingWithoutWildCards(wildCardList); // or:
doTheThingWithoutWildCards(Arrays.asList(1, 2, 3D));
Глава о вводе типа не читается легко. Я не знаю, есть ли разница между подстановочными знаками и другими типами в этом отношении, но я не думаю, что это должно быть. Таким образом, его ошибка компилятора или ограничение по спецификации, но логически, нет причина, почему шаблон должен работать.
Ответ 3
Это связано с типом вывода. В первом случае вы объявили List<Number>
, поэтому компилятор ничего не имеет против, когда вы пишете number -> Integer.valueOf(number.intValue())
, потому что type переменной number
является java.lang.Number
Но во втором случае вы объявили final List<? extends Number> wildCardList
, из-за чего
Collectors.toMap
преобразуется в нечто вроде Collectors.<Object, ?, Map<Object, Number>toMap
например.
final List<? extends Number> wildCardList = Arrays.asList(1, 2, 3D);
Collector<Object, ?, Map<Object, Object>> collector = Collectors.toMap(
// Why is number treated as an Object and not a Number?
number -> Integer.valueOf(number.intValue()),
number -> number);
wildCardList.stream().collect(collector);
В результате чего в выражении
number -> Integer.valueOf(number.intValue()
тип переменной number
- Объект, и в классе Object отсутствует метод intValue()
. Следовательно, вы получаете ошибку компиляции.
Вам нужно передать аргументы типа коллектора, которые помогают компилятору разрешить ошибку intValue()
Например:
final List<? extends Number> wildCardList = Arrays.asList(1, 2, 3D);
Collector<Number, ?, Map<Integer, Number>> collector = Collectors.<Number, Integer, Number>toMap(
// Why is number treated as an Object and not a Number?
Number::intValue,
number -> number);
wildCardList.stream().collect(collector);
Кроме того, вы можете использовать ссылку метода Number::intValue
вместо number -> Integer.valueOf(number.intValue())
Более подробную информацию о Type Inference в Java 8 см. здесь.