收集以跳过空键/值的映射

时间:2018-04-03 15:09:32

标签: java java-8 java-stream

假设我有一些流,想要像这样收集地图

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));

但是有更好的/有效的解决方案吗?

5 个答案:

答案 0 :(得分:6)

如果您想避免两次评估函数func1func2,则必须存储结果。 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));

这不会使代码更短,甚至效率取决于具体情况。如果评估func1func2的成本足以补偿临时对象的创建,则此更改可获得回报。原则上,临时对象可以被优化掉,但这不能保证。

从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) -> {});