如何在流中使用 Map.computeIfAbsent()?

How to use Map.computeIfAbsent() in a stream?

我有一个有趣的益智游戏。假设我有一个 String 值的列表:

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

然后我必须向另一个系统查询 Map<User, Long> 用户 ,其属性对应于 list[=41] 中的那些值=] count:

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

我需要 return 一个新的 List<UserCount>,每个 key 都有一个计数。所以我预计:

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

但是当我的一个 User 对象在 map.[=21= 中没有对应的 count 时,我很难计算]

我知道 map.computeIfAbsent() 可以满足我的需要,但我如何根据 原始列表的内容应用它 ?

我想我需要流式传输原始列表,然后应用计算?所以我有:

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

但这就是我卡住的地方。任何人都可以提供有关我如何完成我需要的任何见解吗?

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());

您可以创建一个辅助 Map<String, Long>,它将每个 字符串键 count 相关联,然后生成一个列表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() 与流一起使用的想法 - 这种方法是错误的,documentation of the 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());

而不是在 maplist 上应用 forEach(),您可以创建两个增强的 for如果此选项看起来很复杂,则循环。

要使用 List<String>Map<User, Long> 中获取 List<UserCount>,您可以按原样流式传输 List<String>,然后将每个键映射到 UserCount.

基本上,对于每个列表的键,您可以通过return用户使用相应的键来检查它是否存在于地图的键集中。要从地图 return User ,您可以简单地流式传输键集,按当前键过滤它并使用终端操作 findFirst() 获得包含唯一的 Optional User 使用该键或不使用该键(如果包含或为空,orElse(null) 将 return 为 User。检索用户后,您现在可以使用您正在映射的当前键创建一个新的 UserCount,如果用户为空则为 0L,如果不为空则为相应的 long 值。

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 类 与 Lombok 的 @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());
    }
}