简体   繁体   English

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

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

I would like to get the item after the last occurence of specific item in list.我想在列表中最后一次出现特定项目后获取该项目。 eg例如

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.
}}

I want to get the item after the last occurence of a Bean with null text eg Bean id 6 in the above.我想在最后一次出现带有 null 文本的Bean之后获取该项目,例如上面的 Bean id 6。

Many thanks.非常感谢。

As it was mentioned in the comments, Streams are not a good solution for this task.正如评论中提到的,Streams 不是这个任务的好解决方案。 But it's still possible to do with them using Atomic :但是仍然可以使用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());

I'm with RealSkeptic on streams not being the best for this problem.我与 RealSkeptic 在流上不是最好的解决这个问题。 If you have to do it, though, you could iterate over your list in reverse, filter to find the first null text, and then pick the one preceding it.但是,如果您必须这样做,您可以反向遍历您的列表,过滤以找到第一个 null 文本,然后选择它前面的一个。 It's more practical with a stream of indices rather than the stream of list elements:使用索引的 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);

Without streams:没有流:

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;
  }
}

You can also iterate the list in reverse, using a ListIterator , and stop as soon as you find the first match.您还可以使用ListIterator反向迭代列表,并在找到第一个匹配项后立即停止。

One of the workarounds for using the Stream would be to keep track of last index of text s from Bean .使用Stream的解决方法之一是跟踪Beantext的最后索引。

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

This way you can easily then access the next element after certain text's last index.这样您就可以轻松地访问某些文本的最后一个索引之后的下一个元素。

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

The drawback especially in your case if the null text value which I am turning into the null keys in the Map .如果将 null 中的null键转换为null文本值,则缺点尤其Map They should be highly discouraged and for that reason you can choose to implement a wrapper to convert null to some default text and then lookup based on the same as well.他们应该被强烈劝阻,因此您可以选择实现一个包装器来将null转换为一些默认文本,然后也基于相同的文本进行查找。


Gotchas陷阱

  1. One of the gotchas with the above approach is that you could get an array index out of bounds exception while trying to access上述方法的一个问题是,您可能会在尝试访问时使数组索引超出范围异常

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

and the key text is also present in the last element of the list.并且关键文本也出现在列表的最后一个元素中。

  1. Another point as Holger has also mentioned in the comments explicitly is that this approach prepares a lot of information. Holger 在评论中也明确提到的另一点是,这种方法准备了大量信息。

    Note there is a trade-off in terms of what your queries further might be.请注意,在您的进一步查询可能是什么方面存在权衡。 If you want to prepare for lookups of list of texts.如果您想准备查找文本列表。 This approach might be useful for a single iteration and faster access.这种方法可能对单次迭代和更快的访问有用。

    But on the other hand, if it's just a one-time computation then its worth minimizing the information to just the single index.但另一方面,如果它只是一次计算,那么将信息最小化为单个索引是值得的。 Holger has already suggested a solution to that as well. Holger 也已经提出了解决方案。

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

Another solution is:另一种解决方案是:

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 version: 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);

Using partitioningBy .使用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);

This relies on positioned elements.这依赖于定位的元素。 This throws a NoSuchElementException or an ArrayIndexOutOfBoundsException if such element does not exist.如果此类元素不存在,则会引发NoSuchElementExceptionArrayIndexOutOfBoundsException


Alternatively, instead of + 1 , you could also just do .mapToObj(i -> i + 1) instead of .boxed() , which yields the same results.或者,代替+ 1 ,您也可以只做.mapToObj(i -> i + 1)而不是.boxed() ,这会产生相同的结果。

I would simply start at the end and work backwards.我会简单地从最后开始并向后工作。 Note that this starts on the next to last element.请注意,这从倒数第二个元素开始。 If the last element were null there would be nothing to return.如果最后一个元素是 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