Java 8 Streams: почему Collectors.toMap ведет себя по-разному для дженериков с помощью подстановочных знаков?

Предположим, что у вас есть List номеров. Значения в List могут иметь тип Integer , Double и т. Д. Когда вы объявляете такой List его можно объявить с помощью подстановочного знака ( ? ) Или без подстановочного знака.

 final List numberList = Arrays.asList(1, 2, 3D); final List wildcardList = Arrays.asList(1, 2, 3D); 

Итак, теперь я хочу stream List и collect все его на Map с помощью Collectors.toMap (очевидно, приведенный ниже код является лишь примером для иллюстрации проблемы). Давайте начнем с streamовой передачи numberList :

 final List 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 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

Из compiler errors очевидно, что number в lambda рассматривается как Object а не как Number .

Итак, теперь на мой вопрос (ы):

  • При сборе подстановочной версии List , почему он не работает как несимметричная версия List ?
  • Почему переменная number в лямбде считается Object вместо Number ?

Это тип вывода, который не понимает. Если аргумент типа явно указан явно, он работает так, как ожидалось:

 List wildCardList = Arrays.asList(1, 2, 3D); wildCardList.stream().collect(Collectors.toMap( number -> Integer.valueOf(number.intValue()), number -> number)); 

Это известная ошибка javac: вывод не должен отображать переменные захвата в их верхние границы . Статус, по словам Маурицио Чимадамора,

исправление было предпринято, а затем было отменено, поскольку оно нарушало случаи в 8, поэтому мы пошли на более консервативное исправление в 8, выполняя всю вещь в 9

По-видимому, исправление еще не было нажато. (Спасибо Джоэлу Боргрен-Франку за то, что он указал мне в правильном направлении.)

Объявление формы List wildcardList List wildcardList означает «список с неизвестным типом, который является Number или подclassом Number ». Интересно, что тот же самый список с неизвестным типом работает, если неизвестный тип ссылается на имя:

 static  void doTheThingWithoutWildCards(List 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 или подclass Number », но вы можете обработать List по назначению. Вы можете назначить List List to List без проблем, поскольку ограничение, которое неизвестный тип extends Number , совместимо.

 final List wildCardList = Arrays.asList(1, 2, 3D); doTheThingWithoutWildCards(wildCardList); // or: doTheThingWithoutWildCards(Arrays.asList(1, 2, 3D)); 

Глава о вводе типа не читается легко. Я не знаю, есть ли разница между подстановочными знаками и другими типами в этом отношении, но я не думаю, что должно быть. Таким образом, это либо ошибка компилятора, либо ограничение по спецификации, но логически, нет причин, по которым шаблон не должен работать.

Это связано с типом вывода. В первом случае вы объявили List поэтому компилятор ничего не имеет против, когда вы пишете number -> Integer.valueOf(number.intValue()) потому что тип переменной number – это java.lang.Number

Но во втором случае вы объявили final List wildCardList final List wildCardList благодаря которому Collectors.toMap переводится на что-то вроде Collectors.toMap Eg

  final List wildCardList = Arrays.asList(1, 2, 3D); Collector> 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()

тип переменной – это Object и в classе Object отсутствует метод intValue() . Следовательно, вы получаете ошибку компиляции.

Вам нужно передать аргументы типа коллектора, которые помогают компилятору разрешить ошибку intValue() Например

  final List wildCardList = Arrays.asList(1, 2, 3D); Collector> collector = Collectors.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 см. Здесь .

Ты можешь сделать:

 final List numberList = Arrays.asList(1, 2, 3D, 4D); numberList.stream().collect(Collectors.toMap(Number::intValue, Function.identity()));