Потоки 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()));
Другие вопросы по тегам