有没有一种优雅的方法将Map <P,Optional <Q >>转换为稀疏Map <P,Q>?

Pau*_*ens 19 java functional-programming java-8 java-stream

是否有一种优雅的方式将其转换Map<P, Optional<Q>>为稀疏Map<P, Q>

这应该工作,但它有点meh:

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()));
Run Code Online (Sandbox Code Playgroud)

Pol*_*ome 10

我会说你的方式几乎已经是最优雅的方式,我只做一些轻微的化妆品改变,并取代e -> e.getKey()你的收藏家Entry::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()));
Run Code Online (Sandbox Code Playgroud)

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

因为它们不是更简洁,它们又陷入了不宣布你想要做什么的陷阱,而是如何在程序风格中常见,但在功能性风格中则不然.

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

如果你看一下其他解决方案,那些会颠倒逻辑流程并使用程序化的思维方式:

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

这只是稍微短一点:

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))
Run Code Online (Sandbox Code Playgroud)

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

此外,功能方法可以更好地扩展 - 如果您拥有大量数据,则切换到并行流是非常简单的,将foreach-approach 转换为并行需要重写功能filter/ collect方法.这与这种轻量级操作无关(事实上,这里不做,可能更慢),但在其他情况下可能是一个理想的特性.

在我看来,使用功能filter/ collect方法比使用程序更可取foreach,因为你训练自己使用好习惯.但请记住,"优雅"往往是旁观者的眼睛.对我来说,更"优雅"的方式是没有副作用的正确功能方式.因人而异.

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

Zhe*_*lov 9

这个怎么样:

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


ern*_*t_k 5

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

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

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);
Run Code Online (Sandbox Code Playgroud)

输出是{four=4, one=1, two=2}(当map2.compute得到nullq.orElse,p不被添加到map2)