Подтвердить что ты не робот

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?
4b9b3361

Ответ 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 см. здесь.