Java 流合并或减少重复对象

Posted

技术标签:

【中文标题】Java 流合并或减少重复对象【英文标题】:Java stream merge or reduce duplicate objects 【发布时间】:2017-05-23 05:18:09 【问题描述】:

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

预期输出 1. 朋友 - [姓名:“Christian Bale”,出生日期:“1970-01-01”,liAttribute:“..”,fbAttribute:“..”,twitterAttribute:“..”] 2. 朋友 - [姓名:“约翰尼·德普”,出生日期:“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());

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

【问题讨论】:

【参考方案1】:

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

由于嵌套收集器代表一个归约(另见this answer),我们可以使用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) -&gt; friendMergeFunction(a,b)替换为DeclaringClass::friendMergeFunctionthis::friendMergeFunction


但请注意,即使在您原来的方法中,也可以进行一些简化。当您只处理Map 的值时,您不需要使用entrySet(),这需要您在每个条目上调用getValue()。您可以首先处理values()。然后,您不需要冗长的 input -&gt; return expression; 语法,因为 input -&gt; 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 创建,因为这是不可避免的。它只会跳过代表每个组的 Lists 的创建,因为它会将它们减少为单个 Friend 就地。

【讨论】:

嗨 Holger,也许你可以用Collectors.toMap 替换Collectors.groupingBy,类似Map&lt;String, Friend&gt; uniqueFriendMap = friends.stream().collect(Collectors.toMap(Friend::uniqueFunction, Function.identity(), this::friendMergeFunction));,你不觉得更简单吗? 关于 Nicolas 的评论,我认为 uniqueFunction 可能会在哈希上产生冲突,这将迫使您编写一个不真正代表完整对象的 equals 方法。 @Dave 我不知道您所说的“uniqueFunction 可能会在哈希上产生冲突”是什么意思。该函数返回字符串并合并那些 uniqueFunction 返回相同字符串的Friend 实例,作为本题的任务。 @Holger 如果您要合并的 Friend 实例有不同的来源,但您仍想合并它们,那么您需要编写不代表完整对象的 hashcode 和 equals 方法。在这种情况下可以正常工作,但不是标准的做法。 @Dave 看来,您在这里混淆了事物。对于HashMap,只有 keys 的哈希码和 equals 方法是相关的,正如我之前的评论中已经说过的,这里的键是 String 方法返回的实例 @ 987654352@。没有“完整对象”用作键,只有一个属性。将被合并的是地图的 ,其等于或哈希码实现完全不相关。在你没有解释你的术语“在哈希上产生冲突”之后,你引入了另一个术语,“有不同的来源”......

以上是关于Java 流合并或减少重复对象的主要内容,如果未能解决你的问题,请参考以下文章

java list的重复对象怎么去除

通过流将带有列表的列表对象转换为Java 8中的映射[重复]

如何在java-8中查找流类型[重复]

java 怎么把多个list 合并成一个去掉重复的

java 怎么把多个list 合并成一个去掉重复的

Java 8流到文件[重复]