从 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 工作。如果您想要不同大小的结果列表,则必须明确filter
或flatMap
。
你说的是真的!好吧,我的问题不再有意义了……哈哈哈
这很简单。此外,如果您只想过滤空返回值,那么我们也可以将.filter(out -> out != null)
简化为.filter(Objects::nonNull)
。【参考方案2】:
将map
调用替换为flatMap
。 map
操作为每个输入值生成一个输出值,而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<OptionalInt>
。然后你需要flatMap
那个流来删除空的选项:
List<Integer> output = input.stream()
.map(this::crazyFunctionReturningOptionalInt)
.flatMap(o -> o.isPresent() ? Stream.of(o.getAsInt()) : Stream.empty())
.collect(toList());
flatMap
的结果是Stream<Integer>
,它将int
s 打包,但这没关系,因为您要将它们发送到List
。如果您不打算将 int
值装箱为 List
,则可以使用以下命令将 Stream<OptionalInt>
转换为 IntStream
:
flatMapToInt(o -> o.isPresent() ? IntStream.of(o.getAsInt()) : IntStream.empty())
有关处理选项流的进一步讨论,请参阅this question and its answers。
【讨论】:
在这里使用 flatMap 而不是简单地使用.filter(o -> o.isPresent())
过滤掉空值有什么好处吗?
首先不创建空项表示显然是一个优势。
@Eran 我认为主要是风格问题。如果您有一个有时不想返回结果的映射操作,使用flatMap
返回零或一值可能是有意义的。将Optional
值返回到流,然后是filter(Optional::isPresent).map(Optional::get)
可以工作并且简单明了,但可能与可能返回无值的映射器不匹配。但这肯定是一种价值技术。
@StuartClark:如果您想知道哪个更快,请测量,不要猜测,尤其不要基于对流如何工作的基本误解而猜测。没有“再次解析流中的值”之类的东西。
@Holger,@StuarClark:我测量了map(...).filter(out -> out!=null && !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.<Integer>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 映射操作返回空元素的主要内容,如果未能解决你的问题,请参考以下文章