Как использовать Map.computeIfAbsent() в потоке?

У меня есть забавная головоломка. Скажем, у меня есть список значений String:

["A", "B", "C"]

Затем я должен запросить у другой системы Map<User, Long> из пользователи с атрибутом, который соответствует этим значениям в список с считать:

{name = "Annie", key = "A"} -> 23
{name = "Paul", key = "C"} -> 16

Мне нужно вернуть новый List<UserCount> со счетчиком каждого ключ. Итак, я ожидаю:

 {key = "A", count=23},
 {key = "B", count=0},
 {key = "C", count=16}

Но мне трудно вычислять, когда один из моих User объектов не имеет соответствующего count в карта.

Я знаю, что map.computeIfAbsent() делает то, что мне нужно, но как я могу применить его на основе что находится в содержимом исходного списка?

Я думаю, мне нужно передать исходный список, а затем применить вычисление? Так что я:

valuesList.stream()
 .map(it -> valuesMap.computeIfAbsent(it.getKey(), k-> OL))
 ...

Но вот где я застрял. Может ли кто-нибудь дать представление о том, как мне выполнить то, что мне нужно?

Вы имеете в виду что-то вроде этого? List<UserCount> list = valuesList.stream().map(it -> new UserCount(it.getKey(), valuesMap.computeIfAbsent(it.getKey(), k -> 0L))).collect(Collectors.toList()) (при условии, что UserCount имеет конструктор UserCount(String key, long count)

Lino 13.05.2022 16:39
valuesList это List<String> и valuesMap это Map<User, Long> правильно? Тогда откуда it.getKey()? it должен быть String и не иметь getKey метода.
Sweeper 13.05.2022 16:40
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
2
90
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

final Map<User,Long> valuesMap = ...

// First, map keys to counts (assuming keys are unique for each user) 
final Map<String,Long> keyToCountMap = valuesMap.entrySet().stream()
    .collect(Collectors.toMap(e -> e.getKey().key, e -> e.getValue()));

final List<UserCount> list = valuesList.stream()
  .map(key -> new UserCount (key, keyToCountMap.getOrDefault(key, 0L)))
  .collect(Collectors.toList());

Как это учитывает элемент, отсутствующий на карте?

CNDyson 13.05.2022 16:44

Вы правы @CNDyson, я пропустил этот момент. Я отредактировал свой ответ соответственно

Michail Alexakis 13.05.2022 17:15
Ответ принят как подходящий

Вы можете создать вспомогательный Map<String, Long>, который будет связывать каждый строковый ключ с считать, а затем создавать список UserCount на его основе.

Пример:

public record User(String name, String key) {}
public record UserCount(String key, long count) {}

public static void main(String[] args) {
    List<String> keys = List.of("A", "B", "C");
    
    Map<User, Long> countByUser =
        Map.of(new User("Annie", "A"), 23L,
               new User("Paul", "C"), 16L));
    
    Map<String, Long> countByKey = countByUser.entrySet().stream()
        .collect(Collectors.groupingBy(entry -> entry.getKey().key(), 
            Collectors.summingLong(Map.Entry::getValue)));

    List<UserCount> userCounts = keys.stream()
        .map(key -> new UserCount(key, countByKey.getOrDefault(key, 0L)))
        .collect(Collectors.toList());

    System.out.println(userCounts);
}

Выход

[UserCount[key=A, count=23], UserCount[key=B, count=0], UserCount[key=C, count=16]]

Что касается идеи использования computeIfAbsent() с потоком — этот подход неверен и не рекомендуется документация по Stream API.

Конечно, вы можете использовать computeIfAbsent() для решения этой проблемы, но не в сочетании с потоками. Не рекомендуется создавать поток, работающий через побочные эффекты (по крайней мере без веской причины).

И я думаю, вам даже не нужна Java 8 computeIfAbsent(), будет достаточно простой и простой putIfAbsent().

Следующий код даст тот же результат:

Map<String, Long> countByKey = new HashMap<>();

countByUser.forEach((k, v) -> countByKey.merge(k.key(), v, Long::sum));
keys.forEach(k -> countByKey.putIfAbsent(k, 0L));

List<UserCount> userCounts = keys.stream()
            .map(key -> new UserCount(key, countByKey.getOrDefault(key, 0L)))
            .collect(Collectors.toList());

И вместо того, чтобы применять forEach() к карта и список, вы можете создать две расширенные петли for, если эти параметры выглядят запутанными.

Огромное спасибо! «Вспомогательный HashMap» был подсказкой, в которой я нуждался!

CNDyson 13.05.2022 18:52

Чтобы получить List<UserCount> из вашего Map<User, Long> с помощью List<String>, вы можете передать List<String>, как вы это делали, а затем сопоставить каждый ключ с UserCount.

По сути, для каждого ключа списка вы можете проверить, присутствует ли он в наборе ключей карты, вернув пользователя с соответствующим ключом. Чтобы вернуть User с карты, вы можете просто передать набор ключей, отфильтровать его по текущему ключу и использовать операцию терминала findFirst(), чтобы получить Optional, содержащий единственный User с этим ключом или ничего (orElse(null) вернет User, если он содержится или равен нулю ). После получения пользователя теперь вы можете создать новый UserCount с текущим ключом, который вы сопоставляете, и 0L, если пользователь имеет значение null, или его соответствующее длинное значение, если оно не равно нулю.

List<UserCount> listRes = listKeys.stream()
        .map(key -> {
            //Retrieving the user whose key is equal to the current one we're mapping, if no user has been found we return null
            User user = mapUsers.keySet().stream().filter(u -> u.getKey().equals(key)).findFirst().orElse(null);

            //Returning a UserCount with key equals to the one we're mapping and with count equals to its corresponding value (if the user is not null) or 0 (if the user is null)
            return new UserCount(key, user != null ? mapUsers.get(user) : 0L);
        })
        .collect(Collectors.toList());

Здесь также есть образец

https://ideone.com/FNm9XS

Еще одна образовательная и параллельная версия — собрать логику в одном месте и создать собственный накопитель и объединитель для коллектора.

public static void main(String[] args) {

    Map<User, Long> countByUser =
            Map.of(new User("Alice", "A"), 23L,
                    new User("Bob", "C"), 16L);

    List<String> keys = List.of("A", "B", "C");

    UserCountAggregator userCountAggregator =
            countByUser.entrySet()
                    .parallelStream()
                    .collect(UserCountAggregator::new,
                            UserCountAggregator::accumulator,
                            UserCountAggregator::combiner);

    List<UserCount> userCounts = userCountAggregator.getUserCounts(keys);
    System.out.println(userCounts);
}

Выход

[UserCount(key=A, count=23), UserCount(key=B, count=0), UserCount(key=C, count=16)]

Классы User и UserCount с @Value Ломбока

@Value
class User {
    private String name;
    private String key;
}

@Value
class UserCount {
    private String key;
    private long count;
}

И UserCountAggregator, который содержит ваш пользовательский аккумулятор и объединитель

class UserCountAggregator {
    private Map<String, Long> keyCounts = new HashMap<>();

    public void accumulator(Map.Entry<User, Long> userLongEntry) {
        keyCounts.put(userLongEntry.getKey().getKey(),
                keyCounts.getOrDefault(userLongEntry.getKey().getKey(), 0L) 
                        + userLongEntry.getValue());
    }

    public void combiner(UserCountAggregator other) {
        other.keyCounts
                .forEach((key, value) -> keyCounts.merge(key, value, Long::sum));
    }

    public List<UserCount> getUserCounts(List<String> keys) {
        return keys.stream()
                .map(key -> new UserCount(key, keyCounts.getOrDefault(key, 0L)))
                .collect(Collectors.toList());
    }
}

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