我会说你的方法已经是最优雅的了,我只会做一些轻微的美化并将你收集器中的 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()));
为什么其他解决方案不更好/更优雅?
因为它们不够简洁,并且它们再次陷入了一个陷阱,即没有声明你想要做什么,而是如何做,这在过程式编程风格中很常见,但在函数式编程中则不是那么常见。
如果您查看上面的代码,它几乎是自说明的,并且具有良好的流程。您首先有一个带有 Optional 的非稀疏映射,然后声明没有 Optional 的稀疏映射,然后描述前者映射到后者的转换。这也没有副作用。只有在收集器实际完成时才会分配稀疏映射。
如果您查看其他解决方案,则将反转逻辑流,并使用过程式思维方式。
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
方案可以节省一些字符,但最终,如果格式合理,两种 foreach
解决方案都需要 4 行代码,因此它们并不比函数式的解决方案更短。它们也不更清晰。相反,它们依赖于在其他 map 中引起副作用。这意味着在计算过程中,会将部分构造好的稀疏 map 分配给您的变量。使用函数式的解决方案时,只有在正确构造 map 时才会进行分配。这只是小小的一点问题,在这种情况下可能不会引起问题,但在其他情况下可能会产生影响(例如当涉及并发性时),特别是当另一个 map 不是局部变量而是字段 — 或者更糟糕时,从其他地方传递而来。
此外,函数式方法具有更好的可扩展性-如果您有大量数据,并行流的切换非常简单,将 foreach
-方法转换为并行需要重写为函数式的 filter
/collect
方法。虽然在这种轻量级操作中并不相关(实际上,在这里不要这么做,它可能会更慢),但在其他情况下,这可能是一个可取的特征。
在我看来,使用函数式的 filter
/collect
方法比使用过程式的 foreach
更可取,因为您可以养成好习惯。但请记住,“优美”常常在观察者的眼中。对我而言,更“优美”的方式是适当的函数式方式,它没有副作用。但你可能会有不同的看法。
map.forEach((key, optional) -> optional.ifPresent(value -> map2.put(key, value)));
但是必须先初始化map2
:Map<P, Q> map2 = new HashMap<>();
- Lino