У меня есть забавная головоломка. Скажем, у меня есть список значений 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))
...
Но вот где я застрял. Может ли кто-нибудь дать представление о том, как мне выполнить то, что мне нужно?
valuesList это List<String> и valuesMap это Map<User, Long> правильно? Тогда откуда it.getKey()? it должен быть String и не иметь getKey метода.




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, я пропустил этот момент. Я отредактировал свой ответ соответственно
Вы можете создать вспомогательный 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» был подсказкой, в которой я нуждался!
Чтобы получить 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());
Здесь также есть образец
Еще одна образовательная и параллельная версия — собрать логику в одном месте и создать собственный накопитель и объединитель для коллектора.
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());
}
}
Вы имеете в виду что-то вроде этого?
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)