Потоки Java 8: почему 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
это все для Map
с использованием 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: не могу найти символ
символ: метод intValue ()
location: переменная номер типа java.lang.Object
Из ошибки компилятора очевидно, что number
в лямбде трактуется как Object
а не как Number
,
Итак, теперь на мой вопрос (ы):
- При сборе подстановочной версии
List
почему он не работает, как не подстановочная версияList
? - Почему
number
переменная в лямбда считаетсяObject
вместоNumber
?
4 ответа
Это вывод типа, который не понимает это правильно. Если вы предоставляете аргумент типа явно, он работает как положено:
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 за то, что он указал мне правильное направление.)
Декларация формы 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>
к List<N>
без проблем как ограничение, что неизвестный тип extends Number
совместимо
final List<? extends Number> wildCardList = Arrays.asList(1, 2, 3D);
doTheThingWithoutWildCards(wildCardList); // or:
doTheThingWithoutWildCards(Arrays.asList(1, 2, 3D));
Глава о выводе типов не легко читается. Я не знаю, есть ли разница между подстановочными знаками и другими типами в этом отношении, но я не думаю, что это должно быть. Так что это либо ошибка компилятора,либо ограничение по спецификации, но логически, нет никаких причин, почему подстановочный знак не должен работать.
Это связано с выводом типа. В первом случае вы объявили List<Number>
так что компилятор ничего не имеет против когда пишешь number -> Integer.valueOf(number.intValue())
потому что тип переменной 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
это объект и нет метода intValue()
определено в классе Object. Следовательно вы получаете ошибку компиляции.
Вам нужно передать аргументы типа сборщика, которые помогут компилятору разрешить 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())
Для получения более подробной информации о выводе типа в Java 8, пожалуйста, обратитесь сюда.
Ты можешь сделать:
final List<Number> numberList = Arrays.asList(1, 2, 3D, 4D);
numberList.stream().collect(Collectors.toMap(Number::intValue, Function.identity()));