143

如何在以下代码中获取流或列表的最后一个元素?

哪里data.careasList<CArea>

CArea first = data.careas.stream()
                  .filter(c -> c.bbox.orientationHorizontal).findFirst().get();

CArea last = data.careas.stream()
                 .filter(c -> c.bbox.orientationHorizontal)
                 .collect(Collectors.toList()).; //how to?

正如你所看到的,获得第一个元素filter并不难。

然而,在单行中获取最后一个元素是一个真正的痛苦:

  • 看来我无法直接从Stream. (这只对有限流有意义)
  • 似乎您也无法从界面中获取诸如first()和之类的东西,这确实很痛苦。last()List

我看不到在接口中不提供first()andlast()方法的任何论据List,因为那里的元素是有序的,而且大小是已知的。

但根据原始答案:如何获得有限的最后一个元素Stream

就个人而言,这是我能得到的最接近的:

int lastIndex = data.careas.stream()
        .filter(c -> c.bbox.orientationHorizontal)
        .mapToInt(c -> data.careas.indexOf(c)).max().getAsInt();
CArea last = data.careas.get(lastIndex);

但是,它确实涉及indexOf在每个元素上使用 an,这很可能不是您通常想要的,因为它会影响性能。

4

8 回答 8

228

可以使用Stream::reduce方法获取最后一个元素。以下清单包含一般情况的最小示例:

Stream<T> stream = ...; // sequential or parallel stream
Optional<T> last = stream.reduce((first, second) -> second);

此实现适用于所有有序流(包括从Lists创建的流)。对于无序的流,由于明显的原因,未指定将返回哪个元素。

该实现适用于顺序流和并行流。乍一看,这可能令人惊讶,不幸的是,文档没有明确说明。然而,它是流的一个重要特性,我试图澄清它:

  • Stream::reduce方法的 Javadoc指出,它不受限于顺序执行”
  • Javadoc 还要求“累加器函数必须是用于组合两个值的关联非干扰无状态函数” ,这显然是 lambda 表达式的情况(first, second) -> second
  • 归约操作的 Javadoc指出:“流类有多种形式的通用归约操作,称为reduce()collect() [..]”“正确构造的归约操作本质上是可并行化的,只要函数) 用于处理元素是关联的和无状态的。”

密切相关的收集器的文档更加明确:“为了确保顺序并行执行产生相同的结果,收集器函数必须满足身份和关联性约束。”


回到最初的问题:以下代码存储了对变量中最后一个元素的引用,last如果流为空则抛出异常。复杂度在流的长度上是线性的。

CArea last = data.careas
                 .stream()
                 .filter(c -> c.bbox.orientationHorizontal)
                 .reduce((first, second) -> second).get();
于 2014-01-29T20:13:04.607 回答
50

如果你有一个 Collection(或更一般的 Iterable),你可以使用 Google Guava's

Iterables.getLast(myIterable)

作为方便的oneliner。

于 2015-10-07T07:34:53.917 回答
12

一个班轮(不需要流;):

Object lastElement = list.isEmpty() ? null : list.get(list.size()-1);
于 2017-08-30T07:48:00.913 回答
12

Guava 针对这种情况有专门的方法:

Stream<T> stream = ...;
Optional<T> lastItem = Streams.findLast(stream);

它相当于stream.reduce((a, b) -> b)但创作者声称它具有更好的性能。

文档

此方法的运行时间将介于 O(log n) 和 O(n) 之间,在高效可拆分流上表现更好。

值得一提的是,如果流是无序的,则此方法的行为类似于findAny().

于 2019-05-07T18:24:36.427 回答
2
list.stream().sorted(Comparator.comparing(obj::getSequence).reversed()).findFirst().get();

颠倒顺序并从列表中获取第一个元素。这里对象有序列号,比较器提供了多种功能,可以按逻辑使用。

于 2021-07-08T03:49:50.267 回答
0

您还可以使用如下的 skip() 函数...

long count = data.careas.count();
CArea last = data.careas.stream().skip(count - 1).findFirst().get();

使用起来超级简单。

于 2018-07-03T10:50:02.857 回答
0

获取最后一个元素的另一种方法是使用排序。

    Optional<CArea> num=data.careas.stream().sorted((a,b)->-1).findFirst();
于 2020-11-04T03:56:49.193 回答
-1

如果需要获取最后 N 个元素。可以使用闭包。下面的代码维护一个固定大小的外部队列,直到流到达末尾。

    final Queue<Integer> queue = new LinkedList<>();
    final int N=5;
    list.stream().peek((z) -> {
        queue.offer(z);
        if (queue.size() > N)
            queue.poll();
    }).count();

另一种选择可能是使用身份作为队列的减少操作。

    final int lastN=3;
    Queue<Integer> reduce1 = list.stream()
    .reduce( 
        (Queue<Integer>)new LinkedList<Integer>(), 
        (m, n) -> {
            m.offer(n);
            if (m.size() > lastN)
               m.poll();
            return m;
    }, (m, n) -> m);

    System.out.println("reduce1 = " + reduce1);
于 2020-05-05T16:51:00.213 回答