【问题标题】:From "Map<String, Collection<String>>" to "Map<String, List<String>>" using Java 8使用 Java 8 从“Map<String, Collection<String>>”到“Map<String, List<String>>”
【发布时间】:2018-08-02 09:09:16
【问题描述】:

有没有更好的方法将“Map>”转换为“Map>”?

Map<String, Collection<String>> collectionsMap = ...
Map<String, List<String>> listsaps =
    collectionsMap.entrySet().stream()
    .collect(Collectors.<Map.Entry<String, Collection<String>>,
        String, List<String>>toMap(
            Map.Entry::getKey,
            e -> e. getValue().stream().collect(Collectors.toList())
        )
    );

感谢您帮助我们改进

【问题讨论】:

  • 我投票决定将此问题作为题外话结束,因为要求审查工作代码的问题应发送至codereview.stackexchange.com
  • @GhostCat 我一直这么认为,但“我如何使用 Java 8 编写 X”问题似乎总是做得很好。

标签: java java-stream collectors entryset


【解决方案1】:

我认为这更容易阅读:

Map<String, List<String>> listsaps = new HashMap<>();
collectionsMap.entrySet()
    .stream()
    .forEach(e -> listsaps.put(e.getKey(), new ArrayList<>(e.getValue())));

如果您只想将条目转换为列表,但并不真正关心更改集合的类型,那么您可以使用map.replaceAll

collectionsMap.replaceAll((k, v) -> new ArrayList<>(v));

【讨论】:

  • 正如@Klitos 所说,replceAll 签名是: void replaceAll(BiFunction super K, ? super V, ? extends V> function) 所以,我无法将结果作为返回值。
  • @RomainDEQUIDT 第二个例子的重点不是创建新地图,而是将现有地图中的集合替换到位。
【解决方案2】:

1) 在Collectors.toMap() 中,您不需要重复泛型类型,因为它们是推断出来的。

所以:

collect(Collectors.<Map.Entry<String, Collection<String>>,
        String, List<String>>toMap(...)

可以替换为:

collect(Collectors.toMap(...)

2) 将集合转化为 List 的方式也可以简化。

这个:

e -> e. getValue().stream().collect(Collectors.toList())

可以写成:

e -> new ArrayList<>(e.getValue())

你可以写:

Map<String, List<String>> listsaps =
            collectionsMap.entrySet()
            .stream()
            .collect(Collectors.toMap(
                    Map.Entry::getKey,
                    e -> new ArrayList<>(e.getValue())
                )
            );

【讨论】:

    【解决方案3】:

    对于这种情况,我会考虑使用Map.forEach 来执行使用副作用的操作。映射上的流有点麻烦,因为需要编写额外的代码来流映射条目,然后从每个条目中提取键和值。相比之下,Map.forEach 将每个键和值作为单独的参数传递给函数。看起来是这样的:

    Map<String, Collection<String>> collectionsMap = ...
    Map<String, List<String>> listsaps = new HashMap<>(); // pre-size if desired
    collectionsMap.forEach((k, v) -> listsaps.put(k, new ArrayList<>(v)));
    

    如果您的地图很大,您可能需要预先确定目的地的大小,以避免在其填充期间重新散列。要正确执行此操作,您必须知道 HashMapbuckets 的数量,而不是 elements 的数量作为其参数。这需要除以默认的加载因子 0.75,以便在给定一定数量的元素的情况下正确地预先调整大小:

    Map<String, List<String>> listsaps = new HashMap<>((int)(collectionsMap.size() / 0.75 + 1));
    

    【讨论】:

    • 好答案,像往常一样。我有一个关于(collectionsMap.size() / 0.75 + 1) 的小问题。我从不理解这种模式,假设您的 collectionsMap 有 16 个存储桶(对于 0.75 load_factor)和 36 个条目(实际使用的 12 个存储桶每个存储桶 3 个)。这个公式将创建(结果为 49,但由于是 2 的下一次幂)64 个桶,比第一个多 4 倍,我也明白没有办法查询地图的桶数......跨度>
    • @Eugene 谢谢。没有问题的问题。 HashMap 的这个领域令人困惑,我总是不得不重新考虑它。 HashMap 将“容量”定义为桶的数量并没有帮助,这非常令人困惑。负载因子是地图中桶数与条目数之间的比率。注意,不是占用的桶数。因此,如果有 100 个桶,并且负载因子为 0.75,则在地图决定调整自身大小并重新散列所有条目之前,最多可以将 75 个条目添加到地图中。无论每个存储桶中有多少条目都是如此。
    • @Eugene 因此,如果我们想向新创建的地图添加 N 个条目,并且我们希望在添加条目时避免调整大小,我们必须创建具有“初始容量”的 HashMap "(桶数)大于 N / 0.75,假设 0.75 作为默认负载因子。所以如果我们想用 75 个元素填充一个 HashMap,我们除以 0.75 并加 1 得到 101 个桶。现在实际上这将进一步四舍五入到 2 的下一个幂,即 128,因此在添加 0.75*128=96 个条目之前不会调整大小。
    • @Eugene 最后,请注意 ConcurrentHashMap 的构造函数采用“初始容量”根据 元素数而不是 buckets数来调整地图大小>。这对于人们经常想要做的事情是有意义的,即用 N 个元素填充地图而不调整大小。但是,它与 HashMap 不同,因此令人困惑。但我认为这首先是 HashMap 造成混淆的错。
    • 哦,我现在感觉自己像个白痴(1)谢谢 - 这很好(2)我可能需要在我们的代码库中搜索我错误引入的东西
    猜你喜欢
    • 1970-01-01
    • 2020-03-17
    • 1970-01-01
    • 2020-08-01
    • 2016-12-03
    • 1970-01-01
    • 1970-01-01
    • 2020-01-26
    • 2018-09-22
    相关资源
    最近更新 更多