【问题标题】:Is there an elegant way to convert a Map<P, Optional<Q>> to a sparse Map<P, Q>?有没有一种优雅的方法可以将 Map<P, Optional<Q>> 转换为稀疏 Map<P, Q>?
【发布时间】:2019-07-11 20:37:33
【问题描述】:

有没有一种优雅的方法可以将Map&lt;P, Optional&lt;Q&gt;&gt; 转换为稀疏的Map&lt;P, Q&gt;

这应该可行,但有点笨拙:

Map<P,Optional<Q>> map = ...;
Map<P,Q> map2 = map.entrySet()
                  .stream().filter(e -> e.getValue().isPresent())
                  .collect(Collectors.toMap(e -> e.getKey(), e->e.getValue().get()));

【问题讨论】:

  • 也许:map.forEach((key, optional) -&gt; optional.ifPresent(value -&gt; map2.put(key, value))); 但必须先初始化 map2Map&lt;P, Q&gt; map2 = new HashMap&lt;&gt;();

标签: java java-8 functional-programming java-stream


【解决方案1】:

我想说你的方式几乎已经是最优雅的方式了,我只会做一些轻微的外观改变,并用Entry::getKey 替换你收集器中的e -&gt; e.getKey()。这只是一个很小的变化,但比其他 lambda 更好地传达您的 意图

Map<P, Optional<Q>> map = new HashMap<>();
Map<P, Q> sparseMap = map.entrySet().stream()
    .filter(e -> e.getValue().isPresent())
    .collect(Collectors.toMap(Entry::getKey, e -> e.getValue().get()));

为什么其他解决方案没有更好/更优雅?

因为它们不是更简洁,而且它们再次陷入了不声明 what 你想做什么,而是 how 的陷阱,这在程序样式中很常见,但在功能性方面则不然。

如果你看一下上面的代码,它几乎是不言自明的,并且有一个很好的流程。你首先有一个带有Optionals 的非稀疏映射,然后声明没有Optionals 的稀疏映射,然后描述前一个映射到后者的转换。这也没有副作用。只有在收集器实际完成时才分配稀疏映射。

如果您查看其他解决方案,那些反转逻辑流程并使用程序思维方式的解决方案:

Map<P, Optional<Q>> map = [....];
Map<P, Q> sparseMap = new HashMap<>();
map.forEach((key, opt) -> opt.ifPresent(value -> sparseMap.put(key, value)));

这只是稍微短了一点:

Map<P, Optional<Q>> map = [....];
Map<P, Q> sparseMap = new HashMap<>();
for (Entry<P, Optional<Q>> e : map.entrySet()) e.getValue().ifPresent(value -> sparseMap.put(key, value))

由于类型推断,您节省了一些字符,但最后,如果您合理地格式化它们,foreach 解决方案都需要 4 个 LOC,因此它们不会比功能性的更短。他们也不清楚。 相反,它们依赖于在另一张地图中造成副作用。这意味着在计算过程中,您会得到一个分配给变量的部分构造的稀疏映射。使用功能解决方案,只有在正确构建地图时才会分配地图。这只是一个小问题,在这种情况下可能不会引起问题,但对于可能变得相关的其他情况(例如,涉及并发时),特别是当其他地图不是局部变量,而是一个字段——或者更糟的是,从其他地方传入。

此外,函数式方法可以更好地扩展 - 如果您有大量数据,切换到并行流是微不足道的,将 foreach- 方法转换为并行需要重写到函数式 filter/collect 方法。这与此类轻量级操作无关(实际上,这里不要这样做,它可能会更慢),但在其他情况下可能是理想的特性。

在我看来,使用功能性filter/collect 方法比使用程序性foreach 更好,因为你训练自己养成良好的习惯。但请记住,“优雅”往往在旁观者的眼中。对我来说,更“优雅”的方式是没有副作用的正确功能方式。 YMMV。

【讨论】:

  • 虽然理论上并行化流的能力很好,但我非常希望看到并行流比顺序流更有效地执行此任务的映射(或只是迭代直接映射)。通常,当条目必须经过一些可以在多个线程之间有效共享的慢速处理时,并行流很有用。然而,仅仅从 Optional 中提取一个值是一个轻量级的操作,并行化它的开销可能会大大超过操作本身的成本。
  • 为什么不把Entry

    > 转化成Entry

    用地图再收集成地图呢?

  • map.forEach“只比for循环短一点”,因为您使用的是原始类型Entry。正确的解决方案必须使用Entry&lt;P, Optional&lt;Q&gt;&gt;,因此代码大小的差异取决于实际的PQ
  • @IlmariKaronen 绝对,在这种情况下无关紧要。这就是为什么我指出要养成良好的习惯。因为当你遇到你需要它的情况时,你不需要与你已经编写的代码不同的代码。
  • @Holger true,会稍微改变一下
【解决方案2】:

我会坚持你所拥有的。它直接表达了意图并利用了流媒体,这是其他答案所缺乏的品质。有时Streams 很冗长,你无能为力。

Map<P,Q> map2 = map.entrySet()
                  .stream().filter(e -> e.getValue().isPresent())
                  .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue().get()));

【讨论】:

    【解决方案3】:

    创建新地图并通过迭代原始地图来添加值会更简单:

    Map<P, Q> map2 = new HashMap<>();
    map.forEach((p, q) -> map2.compute(p, (k, v) -> q.orElse(null)));
    

    所有value.isPresent() 将返回false 的原始条目都将被跳过(不包括在result 中):Map.computeremappingFunction 产生null 时删除/忽略键的映射。


    这里有一个例子来说明如何处理空值:

    Map<String, Optional<Integer>> map = new HashMap<>();
    map.put("one", Optional.of(1));
    map.put("two", Optional.of(2));
    map.put("three", Optional.empty());
    map.put("four", Optional.of(4));
    map.put("five", Optional.empty());
    
    Map<String, Integer> map2 = new HashMap<>();
    map.forEach((p, q) -> map2.compute(p, (k, v) -> q.orElse(null)));
    
    System.out.println(map2);
    

    输出为{four=4, one=1, two=2}(当map2.computeq.orElse得到null时,p不会添加到map2

    【讨论】:

    • @nullpointer 我看到这个特定的方面引起了人们的注意。请阅读我在这篇文章中的最后一句话:-)
    • @ernest_k 仍然null 看起来有点讨厌。如果可能的话,我会避免它。
    • @ZhekaKozlov 我可能没有正确解释。它不是插入空值。我将添加一个示例。
    • 它很短,但我觉得它不太可读。如果我在更大的代码库中看到它,它的作用就不会立即显而易见。
    • @JohnKugelman 感谢您的意见。可读性既是相对的,也是(有时)次要的。
    【解决方案4】:

    这个怎么样:

    Map<P, Q> map2 = new HashMap<>();
    map.forEach((key, opt) -> opt.ifPresent(value -> map2.put(key, value)));
    

    【讨论】:

      猜你喜欢
      • 2014-12-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多