繁体   English   中英

Java 8 Stream - 在特定项目最后出现后获取项目

[英]Java 8 Stream - Get item after the last occurence of specific item

我想在列表中最后一次出现特定项目后获取该项目。 例如

List<Bean>  list = ArrayList<Bean>() {{
   add(new Bean(1, null));  // null
   add(new Bean(2, "text2"));
   add(new Bean(3, "text3"));
   add(new Bean(4, "text4"));
   add(new Bean(5, null));   // null last occurence
   add(new Bean(6, "text6");  // want this one.
}}

我想在最后一次出现带有 null 文本的Bean之后获取该项目,例如上面的 Bean id 6。

非常感谢。

正如评论中提到的,Streams 不是这个任务的好解决方案。 但是仍然可以使用Atomic来处理它们:

    AtomicBoolean isPreviousNull = new AtomicBoolean(false);
    AtomicReference<Bean> lastOccurrence = new AtomicReference<>(null);
    list.forEach(item -> {
        if (item.text == null) {
            isPreviousNull.set(true);
        } else if (isPreviousNull.get()) {
            isPreviousNull.set(false);
            lastOccurrence.set(item);
        }
    });
    System.out.println(lastOccurrence.get());

我与 RealSkeptic 在流上不是最好的解决这个问题。 但是,如果您必须这样做,您可以反向遍历您的列表,过滤以找到第一个 null 文本,然后选择它前面的一个。 使用索引的 ZF7B44CFFAFD5C52223D5498196C8A2E7BZ 而不是列表元素的 stream 更实用:

Bean firstAfterNull = IntStream.iterate(list.size() - 2, i -> i - 1)
        .limit(list.size() - 1) //or better .takeWhile(i -> i >= 0) on Java 9+
        .filter(i -> null == list.get(i).getText())
        .boxed()
        .findFirst()
        .map(i -> i + 1)
        .map(list::get)
        .orElse(null);

没有流:

Bean lastFound = null;

Iterator<Bean> it = list.iterator();
if (it.hasNext()) {
  Bean prev = it.next();
  while (it.hasNext()) {
    Bean curr = it.next();
    if (prev.text == null) {
      lastFound = curr;
    }
    prev = curr;
  }
}

您还可以使用ListIterator反向迭代列表,并在找到第一个匹配项后立即停止。

使用Stream的解决方法之一是跟踪Beantext的最后索引。

Map<String, Integer> lastIndexMap = IntStream.range(0, list.size())
        .boxed()
        .collect(Collectors.toMap(a -> list.get(a).getText(), a -> a, Integer::max));
    

这样您就可以轻松地访问某些文本的最后一个索引之后的下一个元素。

Bean afterLastIndexOfNull = list.get(lastIndexMap.get(null) + 1);
   

如果将 null 中的null键转换为null文本值,则缺点尤其Map 他们应该被强烈劝阻,因此您可以选择实现一个包装器来将null转换为一些默认文本,然后也基于相同的文本进行查找。


陷阱

  1. 上述方法的一个问题是,您可能会在尝试访问时使数组索引超出范围异常

     list.get(lastIndexMap.get(<key>) + 1)

并且关键文本也出现在列表的最后一个元素中。

  1. Holger 在评论中也明确提到的另一点是,这种方法准备了大量信息。

    请注意,在您的进一步查询可能是什么方面存在权衡。 如果您想准备查找文本列表。 这种方法可能对单次迭代和更快的访问有用。

    但另一方面,如果它只是一次计算,那么将信息最小化为单个索引是值得的。 Holger 也已经提出了解决方案。

     OptionalInt lastIndex = IntStream.range(0, list.size()).filter(ix -> list.get(ix).getText() == null).reduce((a, b) -> b);

另一种解决方案是:

Map<String, Bean> map = new HashMap<>();
for (Iterator<Bean> it = list.iterator(); it.hasNext(); )
    if (it.next().getText() == null && it.hasNext()) 
        map.put(null, it.next());
        
System.out.println(map.get(null));

Stream 版本:

lastFound = IntStream.range(0, list.size())
           .collect(() -> new HashMap<String, Bean>(), (m, i) ->
                   {
                      if (list.get(i).getText() == null && (i + 1) < list.size()) 
                        m.put(null, list.get(i + 1));
                   }
                    , HashMap::putAll)
            .get(null);

使用partitioningBy

int index = IntStream.range(0, items.size())
    .boxed()
    .collect(partitioningBy(i -> items.get(i).getText() == null, reducing((l, r) -> r)))
    .get(true).get() + 1;

Bean element = items.get(i);

这依赖于定位的元素。 如果此类元素不存在,则会引发NoSuchElementExceptionArrayIndexOutOfBoundsException


或者,代替+ 1 ,您也可以只做.mapToObj(i -> i + 1)而不是.boxed() ,这会产生相同的结果。

我会简单地从最后开始并向后工作。 请注意,这从倒数第二个元素开始。 如果最后一个元素是 null 则不会返回任何内容。

Optional<Bean> opt = IntStream
        .iterate(list.size() - 2, i -> i >= 0, i -> i - 1)
        .filter(i -> list.get(i).getText() == null)
        .mapToObj(i -> list.get(i + 1)).findFirst();
        
System.out.print(
        opt.isPresent() ? opt.get().getText() : "None Found");

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM