Java 流合并或减少重复对象

Java stream merge or reduce duplicate objects

我需要通过将所有重复的条目合并到一个对象中,从一个可以有重复项的列表中生成一个唯一的朋友列表
示例 - 从不同的社交提要中获取朋友并放入 1 个大列表
1.朋友-[姓名:"Johnny Depp",出生日期:“1970-11-10”,来源:"FB",fbAttribute:“..”]
2.朋友-[姓名:"Christian Bale",出生日期:“1970-01-01”,来源:"LI",liAttribute:“..”]
3.朋友-[姓名:"Johnny Depp",出生日期:“1970-11-10”,来源:"Twitter",twitterAttribute:“..”]
4.朋友-[姓名:"Johnny Depp",出生日期:“1970-11-10”,来源:"LinkedIn",liAttribute:“..”]
5.朋友-[姓名:"Christian Bale",出生日期:"1970-01-01",来源:"LI",liAttribute:".."]

预期输出
1. 好友 - [name: "Christian Bale", dob: "1970-01-01", liAttribute: "..", fbAttribute: "..", twitterAttribute: ".."]
2. 好友-[name:"Johnny Depp",dob:"1970-11-10",liAttribute:"..",fbAttribute:"..",twitterAttribute:".."]

问题 - 我如何在不使用任何中间容器的情况下进行合并?我可以轻松地使用中间映射并对条目的每个值应用 reduce。

List<Friend> friends;
Map<String, List<Friend>> uniqueFriendMap
    = friends.stream().groupingBy(Friend::uniqueFunction);
List<Friend> mergedFriends = uniqueFriendMap.entrySet()
    .stream()
    .map(entry -> {
           return entry.getValue()
                .stream()
                .reduce((a,b) -> friendMergeFunction(a,b));
    })
    .filter(mergedPlace -> mergedPlace.isPresent())
    .collect(Collectors.toList());

我喜欢在不使用中间 Map uniqueFriendMap 的情况下执行此操作。有什么建议吗?

groupingBy操作(或类似的操作)是不可避免的,操作创建的Map也在操作过程中用于查找分组键和查找重复项。但是你可以将它与组元素的减少结合起来:

Map<String, Friend> uniqueFriendMap = friends.stream()
    .collect(Collectors.groupingBy(Friend::uniqueFunction,
        Collectors.collectingAndThen(
            Collectors.reducing((a,b) -> friendMergeFunction(a,b)), Optional::get)));

地图的值已经是生成的不同朋友。如果你真的需要一个 List,你可以用一个普通的 Collection 操作来创建它:

List<Friend> mergedFriends = new ArrayList<>(uniqueFriendMap.values());

如果这第二个操作仍然让您烦恼,您可以将其隐藏在collect操作中:

List<Friend> mergedFriends = friends.stream()
    .collect(Collectors.collectingAndThen(
        Collectors.groupingBy(Friend::uniqueFunction, Collectors.collectingAndThen(
            Collectors.reducing((a,b) -> friendMergeFunction(a,b)), Optional::get)),
        m -> new ArrayList<>(m.values())));

由于嵌套收集器代表一个 Reduction(另见 ),我们可以使用 toMap 代替:

List<Friend> mergedFriends = friends.stream()
    .collect(Collectors.collectingAndThen(
        Collectors.toMap(Friend::uniqueFunction, Function.identity(),
            (a,b) -> friendMergeFunction(a,b)),
        m -> new ArrayList<>(m.values())));

根据 friendMergeFunctionstatic 方法还是实例方法,您可以将 (a,b) -> friendMergeFunction(a,b) 替换为 DeclaringClass::friendMergeFunctionthis::friendMergeFunction


但请注意,即使在您原来的方法中,也可以进行一些简化。当你只处理一个Map的值时,你不需要使用entrySet(),这需要你在每个条目上调用getValue()。您可以首先处理 values() 。然后,您不需要冗长的 input -> { return expression; } 语法,因为 input -> expression 就足够了。由于前面的分组操作的组不能为空,过滤步骤被废弃。所以你原来的方法看起来像:

Map<String, List<Friend>> uniqueFriendMap
    = friends.stream().collect(Collectors.groupingBy(Friend::uniqueFunction));
List<Friend> mergedFriends = uniqueFriendMap.values().stream()
    .map(group -> group.stream().reduce((a,b) -> friendMergeFunction(a,b)).get())
    .collect(Collectors.toList());

还不错。如前所述,融合操作不会跳过 Map 创建,因为这是不可避免的。它只会跳过代表每个组的 List 的创建,因为它会将它们减少到单个 Friend 就地。