【问题标题】:Return empty element from Java 8 map operation从 Java 8 映射操作返回空元素
【发布时间】: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());

【问题讨论】:

    标签: java map java-8 java-stream


    【解决方案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());
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-03-06
        • 1970-01-01
        • 1970-01-01
        • 2019-10-22
        • 2011-03-07
        • 1970-01-01
        • 2021-02-17
        • 1970-01-01
        相关资源
        最近更新 更多