我想说你的方式几乎已经是最优雅的方式了,我只会做一些轻微的外观改变,并用Entry::getKey 替换你收集器中的e -> 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。