假设我有一些流,想要像这样收集地图
stream.collect(Collectors.toMap(this::func1, this::func2));
但我想跳过空键/值。当然,我可以这样做
stream.filter(t -> func1(t) != null)
.filter(t -> func2(t) != null)
.collect(Collectors.toMap(this::func1, this::func2));
但是有更好的/有效的解决方案吗?
答案 0 :(得分:6)
如果您想避免两次评估函数func1
和func2
,则必须存储结果。 E.g。
stream.map(t -> new AbstractMap.SimpleImmutableEntry<>(func1(t), func2(t))
.filter(e -> e.getKey()!=null && e.getValue()!=null)
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
这不会使代码更短,甚至效率取决于具体情况。如果评估func1
和func2
的成本足以补偿临时对象的创建,则此更改可获得回报。原则上,临时对象可以被优化掉,但这不能保证。
从Java 9开始,您可以将new AbstractMap.SimpleImmutableEntry<>(…)
替换为Map.entry(…)
。由于此条目类型从一开始就不允许null
,因此在构造条目之前需要进行过滤:
stream.flatMap(t -> {
Type1 value1 = func1(t);
Type2 value2 = func2(t);
return value1!=null && value2!=null? Stream.of(Map.entry(value1, value2)): null;
})
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
或者,您可以使用您已经使用的库之一的对类型(Java API本身不提供这样的类型)。
答案 1 :(得分:3)
避免两次评估函数的另一种方法。使用a pair class of your choice。不像Holger's那样简洁,但它的密度稍低,可以更容易阅读。
stream.map(A::doFuncs)
.flatMap(Optional::stream)
.collect(Collectors.toMap(Pair::getKey, Pair::getValue));
private static Optional<Pair<Bar, Baz>> doFuncs(Foo foo)
{
final Bar bar = func1(foo);
final Baz baz = func2(foo);
if (bar == null || baz == null) return Optional.empty();
return Optional.of(new Pair<>(bar, baz));
}
(选择合适的名字 - 我不知道你在使用什么类型)
答案 2 :(得分:2)
一个选项是在其他答案中执行,即使用Pair
类型或Map.Entry
的实现。函数式编程中使用的另一种方法是memoize函数。根据维基百科:
memoization或memoisation是一种优化技术,主要用于通过存储昂贵的函数调用的结果来加速计算机程序,并在再次出现相同的输入时返回缓存的结果。
所以你可以通过在地图中缓存函数的结果来实现它:
public static <K, V> Function<K, V> memoize(Function<K, V> f) {
Map<K, V> map = new HashMap<>();
return k -> map.computeIfAbsent(k, f);
}
然后,使用流中的memoized函数:
Function<E, K> memoizedFunc1 = memoize(this::func1);
Function<E, V> memoizedFunc2 = memoize(this::func2);
stream.filter(t -> memoizedFunc1.apply(t) != null)
.filter(t -> memoizedFunc2.apply(t) != null)
.collect(Collectors.toMap(memoizedFunc1, memoizedFunc2));
此处E
代表流元素的类型,K
代表func1
返回的类型(这是地图键的类型)和{ {1}}代表V
返回的类型(这是地图值的类型)。
答案 3 :(得分:0)
您可以在当前类中创建isFunc1AndFunc2NotNull()
方法:
boolean isFunc1AndFunc2NotNull(Foo foo){
return func1(foo) != null && func2(foo) != null;
}
将您的信息流更改为:
stream.filter(this::isFunc1AndFunc2NotNull)
.collect(Collectors.toMap(this::func1, this::func2));
答案 4 :(得分:0)
这是一个天真的解决方案,但不会调用函数两次,也不会创建额外的对象:
List<Integer> ints = Arrays.asList(1, null, 2, null, 3);
Map<Integer, Integer> res = ints.stream().collect(LinkedHashMap::new, (lhm, i) -> {
final Integer integer1 = func1(i);
final Integer integer2 = func2(i);
if(integer1 != null && integer2 != null) {
lhm.put(integer1, integer2);
}
}, (lhm1, lhm2) -> {});