Java8过滤并返回如果只有元素

Posted

技术标签:

【中文标题】Java8过滤并返回如果只有元素【英文标题】:Java8 filter and return if only element 【发布时间】:2018-08-21 12:52:17 【问题描述】:

如何使用java8流过滤列表并返回找到的元素,如果它是过滤列表中唯一的元素,否则(如果满足条件的元素更多,或者没有满足条件的结果)返回例如Optional.empty()

我需要这样的东西:

假设我有一个:

List<String> list = Arrays.asList("Apple","Banana","Peach");

那我想要:

Optional<String> string = list.stream()
    .filter(item -> item.startsWith("A"))
    .findOne();

我知道我可以这样做:

boolean singleElement = list.stream()
        .filter(item -> item.startsWith("A"))
        .count() == 1;

String string = null;

if(singleElement)
  string = list.stream().filter(item -> item.startsWith("A")).iterator().next();

但我想知道是否可以在单个流中完成?

有没有单流解决方案?

【问题讨论】:

您可以将limit 流到2,将其收集到一个列表中,然后检查它是否恰好是一个元素。这仍然有不止一行,但你没有做任何多余的工作。 Filter Java Stream to 1 and only 1 element的可能重复 在我看来,重复问题的最佳答案是this one。 @Sunflame linked answer 的第二部分应该返回一个Optional,没有例外。 @Socowi 这也是我的想法,但我尝试使用reduce,但没有成功。将其添加到我的答案中,只是为了完整性。 【参考方案1】:

不是很漂亮,但是您可以将 limit 流转换为 2 个元素,collect List 中的那些,然后查看该列表是否只有一个元素。这仍然有不止一行,并且有一些创建列表的开销,但是这个开销是有限的(对于大小为 2 的列表)并且它也不必迭代流两次。

List<String> tmp = list.stream()
    .filter(item -> item.startsWith("A"))
    .limit(2)
    .collect(Collectors.toList());
Optional<String> res = tmp.size() == 1 ? Optional.of(tmp.get(0)) : Optional.empty();

(我的另一个想法是在limit(2) 之后使用reduce((s1, s2) -&gt; null) 并将任何两个匹配项减少到null,但不是返回Optional.empty,这只会引发异常,即它确实不 工作,但也许这会引发一些更好的(工作)想法。)


更新:似乎reduce 引发异常,Collectors.reducing 没有,而是根据需要返回Optional.empty,所以这也有效,如this answer 中所示的一个非常相似的问题。尽管如此,我还是会添加limit(2) 让它早点停止:

Optional<String> res = list.stream()
    .filter(item -> item.startsWith("A"))
    .limit(2)
    .collect(Collectors.reducing((s1, s2) -> null));

(如果您喜欢最后一部分,请为原始答案投票。)

【讨论】:

感谢您和@Socowi,这非常有效。 IntellIJ 不喜欢这个有点烦人,但我可以禁用检查 ;) 在 Socowi 向我展示答案后完成了投票:) @tobias_k 我更喜欢这个限制,因为reducing 并不是真的短路。 +1 我也是,没有.limit,1M 字符串的整个过程大约需要 55 毫秒,10M 字符串大约需要 155 毫秒,limit 两者都是大约 40 毫秒。 我不会依赖reducing 收集器的这种行为。没有迹象表明这是故意的。【参考方案2】:

我不知道这对您来说是否算作一次操作,但您可以这样做:

Arrays.asList("Apple", "Banana", "Peach")
            .stream()
            .collect(Collectors.collectingAndThen(
                    Collectors.partitioningBy(
                            x -> x.startsWith("A")),
                    map -> 
                        List<String> list = map.get(Boolean.TRUE);
                        return list.size() == 1 ? Optional.of(list.get(0)) : Optional.empty();
                    ));

【讨论】:

这很重要,但我认为这就像你用大炮寻找苍蝇一样:D filtercollect 之前有什么问题? collectingAndThen 的逻辑也适用于 toList() 收集器和 partitioningBy 收集所有您不感兴趣的元素的行为,这……不是优势。 @Holger 这没什么问题,我在这里搞糊涂了 :(【参考方案3】:

你可以使用谷歌Guava库的MoreCollectors.onlyElement如下:

    List<String> list = Arrays.asList("Apple", "Banana", "Peach");
    String string = null;
    try 
        string = list.stream()
                .filter(item -> item.startsWith("A"))
                .collect(MoreCollectors.onlyElement());
     catch (NoSuchElementException | IllegalArgumentException iae) 
        System.out.println("zero or more than one elements found.");
    
    Optional<String> res = string == null ? Optional.empty() : Optional.of(string);

请注意,如果没有元素,它会抛出 NoSuchElementException,如果有多个元素,它会抛出 IllegalArgumentException

【讨论】:

以上是关于Java8过滤并返回如果只有元素的主要内容,如果未能解决你的问题,请参考以下文章

Java8 新特性 Stream 无状态中间操作

按参数过滤数组

手把手教你如何用java8新特性将List中按指定属性排序,过滤重复数据

如何在Java 8中计算元素并首先找到[重复]

java8 中的时间和数据的变化

java8 增强的Iterator遍历集合元素