从 Java 8 映射操作返回空元素

Posted

技术标签:

【中文标题】从 Java 8 映射操作返回空元素【英文标题】:Return empty element from Java 8 map operation 【发布时间】:2014-09-16 19:18:38 【问题描述】:

当输入 Integer 没有输出时,使用 Java 8 流最好的方法来映射 List<Integer> 是什么?

只返回null?但是现在我的输出列表大小将小于我的输入大小......

    List<Integer> input = Arrays.asList(0,1,2,3);
    List<Integer> output = input.stream()
                                .map(i ->  
                                    Integer out = crazyFunction(i);
                                    if(out == null || out.equals(0))
                                        return null;
                                    return Optional.of(out);
                                    )
                                .collect(Collectors.toList());

【问题讨论】:

【参考方案1】:

我不明白你(以及所有答案)为什么把它弄得这么复杂。您有一个映射操作和一个过滤操作。所以最简单的方法就是一个接一个地应用这些操作。而且除非你的方法已经返回了一个Optional,否则没有必要处理Optional

input.stream().map(i -> crazyFunction(i))
              .filter(out -> out!=null && !out.equals(0))
              .collect(Collectors.toList());

可以简化为

input.stream().map(context::crazyFunction)
              .filter(out -> out!=null && !out.equals(0))
              .collect(Collectors.toList());

但您似乎有一个更理论的问题,即要生成哪种List,一个带有用于缺少值的占位符,或者一个具有与输入列表不同的大小。

简单的答案是:不要生成列表List 本身并不是目的,因此您应该考虑需要此列表(或其内容)的 操作 类型,并将操作权限应用为流的终端操作。然后你就有了答案,因为操作决定了是否应该过滤掉缺失的值或用特殊值表示(以及必须是什么值)。

不同的操作可能会有不同的答案……

【讨论】:

这里有一个小细节:当您的“地图”返回空值时,它不会添加到列表中。所以你在输入中得到的元素比输入少。并且感谢/因为没有必要检查 if out!=null 在你的情况下。不能为空。 @Martin Magakian:你确定吗?在map 函数和toList 收集器上,我没有看到任何null 的自动过滤记录。在我的机器上进行的快速测试显示 map 函数按预期 1:1 工作。如果您想要不同大小的结果列表,则必须明确filterflatMap 你说的是真的!好吧,我的问题不再有意义了……哈哈哈 这很简单。此外,如果您只想过滤空返回值,那么我们也可以将.filter(out -&gt; out != null) 简化为.filter(Objects::nonNull)【参考方案2】:

map 调用替换为flatMapmap 操作为每个输入值生成一个输出值,而flatMap 操作为每个输入值生成任意数量的输出值——包括零。

最直接的方法可能是像这样替换检查:

List<Integer> output = input.stream()
                            .flatMap(i ->  
                                Integer out = crazyFunction(i);
                                if (out == null || out.equals(0))
                                    return Stream.empty();
                                else
                                    return Stream.of(out);
                                )
                            .collect(Collectors.toList());

进一步的重构可能会更改crazyFunction,使其返回Optional(可能是OptionalInt)。如果您从map 调用它,结果是Stream&lt;OptionalInt&gt;。然后你需要flatMap那个流来删除空的选项:

List<Integer> output = input.stream()
    .map(this::crazyFunctionReturningOptionalInt)
    .flatMap(o -> o.isPresent() ? Stream.of(o.getAsInt()) : Stream.empty())
    .collect(toList());

flatMap 的结果是Stream&lt;Integer&gt;,它将ints 打包,但这没关系,因为您要将它们发送到List。如果您不打算将 int 值装箱为 List,则可以使用以下命令将 Stream&lt;OptionalInt&gt; 转换为 IntStream

flatMapToInt(o -> o.isPresent() ? IntStream.of(o.getAsInt()) : IntStream.empty())

有关处理选项流的进一步讨论,请参阅this question and its answers。

【讨论】:

在这里使用 flatMap 而不是简单地使用.filter(o -&gt; o.isPresent()) 过滤掉空值有什么好处吗? 首先不创建空项表示显然是一个优势。 @Eran 我认为主要是风格问题。如果您有一个有时不想返回结果的映射操作,使用flatMap 返回零或一值可能是有意义的。将Optional 值返回到流,然后是filter(Optional::isPresent).map(Optional::get) 可以工作并且简单明了,但可能与可能返回无值的映射器不匹配。但这肯定是一种价值技术。 @StuartClark:如果您想知道哪个更快,请测量,不要猜测,尤其不要基于对流如何工作的基本误解而猜测。没有“再次解析流中的值”之类的东西。 @Holger,@StuarClark:我测量了map(...).filter(out -&gt; out!=null &amp;&amp; !out.equals(0))flatMap(...)。不同 Listsizes 的平均结果(其中比率的第一部分是过滤器方法的测量时间和 flatMap 方法的第二部分,都是近似的)-> 10.000 List-Elements = 120ms:10ms, 100.000 = 130ms :60 毫秒,1.000.000= 160 毫秒:100 毫秒。 10.000.000=500ms:5sec 的大惊喜。所以直觉是,Holgers 的方法在非常庞大的列表中表现出色,比如 10.000.000 个元素。【参考方案3】:

@Martin Magakian 答案的更简单变体:

List<Integer> input = Arrays.asList(0,1,2,3);
List<Optional<Integer>> output =
  input.stream()
    .map(i -> crazyFunction(i)) // you can also use a method reference here
    .map(Optional::ofNullable) // returns empty optional
                               // if original value is null
    .map(optional -> optional.filter(out -> !out.equals(0))) // return empty optional
                                                           // if captured value is zero
    .collect(Collectors.toList())
;

List<Integer> outputClean =
  output.stream()
    .filter(Optional::isPresent)
    .map(Optional::get)
    .collect(Collectors.toList())
;

【讨论】:

【参考方案4】:

您可以将输出包装到 Optional 中,该 Optional 可能包含也可能不包含非空值。 带输出:return Optional.of(out); 无输出:return Optional.&lt;Integer&gt;empty();

您必须换行到一个选项中,因为数组不能包含任何空值

    List<Integer> input = Arrays.asList(0,1,2,3);
    List<Option<Integer>> output = input.stream()
                                .map(i ->  
                                    Integer out = crazyFunction(i);
                                    if(out == null || out.equals(0))
                                        return Optional.<Integer>empty();
                                    return Optional.of(out);
                                    )
                                .collect(Collectors.toList());

这将确保input.size() == output.size()

稍后您可以使用以下方法排除空的 Optional:

    List<Integer> outputClean = output.stream()
                                   .filter(Optional::isPresent)
                                   .map(i -> 
                                           return i.get();
                                        )
                                   .collect(Collectors.toList());

【讨论】:

以上是关于从 Java 8 映射操作返回空元素的主要内容,如果未能解决你的问题,请参考以下文章

在 Java 8 中懒惰地返回第一个非空列表

微信小程序-发布订阅-事件总线-页面组件通信

微信小程序-发布订阅-事件总线-页面组件通信

从 Array(Java) 中删除空元素

操作dom

Java 8 Stream 流 - 使用