Ошибка компиляции при объединении двух Карт выдается для Map.Entry::getKey

Всякий раз, когда я использую Map.Entry::getKey в своих потоках для своих общедоступных методов, я получаю сообщение о том, что мой метод не является static. Я даже пытался сделать свой метод static, и он не работал.

Ниже приведена компиляция ошибка, которую я получаю от использования Map.Entry()::getKey():

Non-static method cannot be referenced from a static context

Мой код

/***
 * Merge 2 Maps and add the values together if they are in both maps
 * 
 * firstMap = {"Eggs": 3, "Cereal": 1}
 * secondMap = {"Eggs": 10, "Coke": 23, "Cereal": 1}
 * 
 * Answer = {"Eggs": 13, "Coke": 23, "Cereal": 2}
 * Notice that the Eggs are now 13
 * 
 * @param firstMap
 * @param secondMap
 * @return
 */
public Map<String, Integer> mergeAndAddValues(Map<String, String> firstMap, Map<String, String> secondMap) {

    return Stream.of(firstMap, secondMap)
            .flatMap(map -> map.entrySet().stream())
            .collect(Collectors.toMap(
                       Map.Entry()::getKey,
                       Map.Entry::getValue,
                       Integer::sum,
                       HashMap::new));
}

Map.Entry()::getKey должен быть Map.Entry::getKey

tgdavies 18.03.2022 04:58

@tgdavies Правильно, но только с этим исправлением это не сработает. Тип элементов потока и тип возвращаемого значения несовместимы.

Alexander Ivanchenko 18.03.2022 18:03
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
2
83
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Вы можете использовать Map.Entry::getKey и Map.Entry::getValue, но ошибка вызвана чем-то другим. Map.Entry::getValue и Integer::sum возвращают разные типы.

Определение метода toMap объясняет, почему:

public static <T, K, U, M extends Map<K, U>> Collector<T, ?, M> toMap(
    Function<? super T, ? extends K> keyMapper, 
    Function<? super T, ? extends U> valueMapper, 
    BinaryOperator<U> mergeFunction, 
    Supplier<M> mapFactory);

В определении можно увидеть, что:

  • valueMapper имеет ? extends U тип
  • mergeFunction имеет U тип

Меняем тип firstMap и secondMap с Map<String, String> на Map<String, Integer> ошибок нет. Это связано с тем, что тип, возвращаемый Map.Entry::getValue, меняется с String на Integer.

Вот окончательный результат:

public Map<String, Integer> mergeAndAddValues(Map<String, Integer> firstMap, Map<String, Integer> secondMap) {
    return Stream.of(firstMap, secondMap)
                 .flatMap(map -> map.entrySet().stream())
                 .collect(Collectors.toMap(
                        Map.Entry::getKey, Map.Entry::getValue, Integer::sum, HashMap::new));
    }
}
Ответ принят как подходящий

Аргументы метода имеют тип Map<String, String>, а возвращаемый тип — Map<String, Integer>.

Чтобы исправить ваш метод, вам нужно преобразовать записи типа Map.Entry<String, String> в записиMap.Entry<String, Integer>.

Это можно сделать, применив операцию map() внутри вложенного потока. Статический метод Map.entry() используется для создания нового вход на основе существующего. Я предполагаю, что все строки состоят только из цифр, иначе вам нужно применить дополнительную очистку перед их разбором.

public Map<String, Integer> mergeAndAddValues(Map<String, String> firstMap,
                                              Map<String, String> secondMap) {

    return Stream.of(firstMap, secondMap)
            .flatMap(map -> map.entrySet().stream()
                    .map(entry -> Map.entry(entry.getKey(),
                            Integer.valueOf(entry.getValue()))))
            .collect(Collectors.toMap(
                       Map.Entry::getKey,
                       Map.Entry::getValue,
                       Integer::sum));
}

Примечание разновидность Collectors.toMap, которая не требует mapFactory (т. е. версия Collectors.toMap, которая принимает только три аргумента), используется, потому что по умолчанию вы получите HashMap в результате.

Предоставление HashMap::new вручную ничего вам не дает, наоборот ваш код становится немного более жестким, и если в будущем HashMap будет заменен более производительной реализацией общего назначения Map вы получите нет бесплатно, за это ваш код надо будет поменять.

I even tried making my method static ... I am getting from using Map.Entry()::getKey()

Non-static method cannot be referenced from a static context

Эта проблема не связана со статическим или экземплярным методом, к сожалению, это редкий случай, когда сообщение об ошибке не очень полезно. Если вы замените эти ссылки на методы лямбда-выражениями, то компилятор правильно укажет, что типы String и Integer несовместимы.

И не надо странных манипуляций с синтаксисом, очень советую ознакомиться с этими туториалами по лямбды и ссылки на методы.

И лямбда-выражения, и ссылки на методы используются для реализации функциональный интерфейс, который представляет собой интерфейс, определяющий один и только один абстрактный метод. т.е. оба лямбда и ссылка на метод должны реализовать этот одиночный метод.

Все аргументы, которые ожидает Collectors.toMap(), являются встроенными в Java функциональные интерфейсы.

В случае, когда имплантация поведения, определенного функциональный интерфейс, уже существует (то есть у вас где-то есть метод, который делает то, что ожидается от метода, объявленного в интерфейсе.), вы можете использовать ее с ссылка на метод.

Есть четыре виды ссылок на методы (цитата из Учебник Оракулов, упомянутого выше):

  • Ссылка на статический метод ContainingClass::staticMethodName
  • Ссылка на метод экземпляра конкретного объекта containingObject::instanceMethodName
  • Ссылка на метод экземпляра произвольного объекта определенного типа ContainingType::methodName
  • Ссылка на конструктор ClassName::new

Примечание, что синтаксис из ссылки на методы не требует круглые скобки ни после типа, ни после имени метода.

Так что правильный синтаксис будет Map.Entry::getKey, и это ссылка на метод экземпляр произвольного объекта определенного типа. т.е. тип Map.Entry на самом деле подразумевает элемент потока (объект, а не интерфейс).

Напоминание:Entry — это вложенный интерфейс, представляющий пара ключ-значение, определенный внутри интерфейса Map. Следовательно, правильный синтаксис для обращения к нему Map.Entry (без скобок).

А getKey — это один из методов экземпляра, объявленных интерфейсом Entry.

Резюме

Правильный синтаксис для определения Function (который является стандартным функциональный интерфейс, ожидаемым в качестве первого аргументаCollectors.toMap() с ссылка на метод:

Map.Entry::getKey

Где первая часть перед двойное двоеточие — это тип объекта, который будет передан в функция. И вторая часть — это имя метода.

Круглые скобки никогда используются после типа в Java (не путайте ссылку на тип с вызовом конструктора). И после имя метода круглые скобки опущены, потому что так был разработан язык, и я думаю, потому что ссылки на методы должны быть краткими и выразительными.

Другие вопросы по теме