单行获取Stream / List的最后一个元素


117

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

哪里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()and last()方法的任何参数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在每个元素上使用on,这通常是您通常不希望的,因为它会影响性能。


10
Guava提供了Iterables.getLastIterable,但经过优化可与一起使用List。令人讨厌的是它没有getFirst。该StreamAPI通常是可怕的肛门,省略了许多便利方法。相比之下,C#的LINQ很乐意提供.Last(),甚至.Last(Func<T,Boolean> predicate),甚至也支持无限的Enumerables。
Aleksandr Dubinsky

@AleksandrDubinsky表示支持,但请读者注意。StreamAPI不能完全与之媲美,LINQ因为两者都是在非常不同的范例中完成的。它并不差或更好,只是不同。肯定没有某些方法不是因为oracle开发人员不称职或刻薄:)
fasth 2015年

1
对于真正的单线而言,此线程可能有用。
量子

Answers:


184

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

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

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

该实现适用于顺序并行流。乍一看这可能令人惊讶,但是不幸的是文档没有明确说明。但是,它是流的重要功能,我尝试对其进行澄清:

  • 方法Stream :: reduce的Javadoc 指出,它不受顺序执行的约束”
  • Javadoc还要求“累加器函数必须是用于组合两个值的关联无干扰无状态函数”,这显然是lambda表达式的情况(first, second) -> second
  • 归约运算的Javadoc 指出:“流类具有多种形式的常规归约运算,分别称为reduce()collect() [..]”“ 只要函数具有适当的构造,reduce运算就具有内在的可并行性。 )用于处理元素是关联的无状态的。”

与密切相关的收集器的文档更加明确:“为了确保顺序并行执行产生相等的结果,收集器功能必须满足标识和关联性约束。”


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

CArea last = data.careas
                 .stream()
                 .filter(c -> c.bbox.orientationHorizontal)
                 .reduce((first, second) -> second).get();

很好,谢谢!您是否知道_在不需要参数的情况下是否可以省略名称(也许使用a 或类似名称)?将会是:.reduce((_, current) -> current)如果只有aws有效语法。
skiwi 2014年

2
@skiwi您可以使用任何合法的变量名称,例如:.reduce(($, current) -> current).reduce((__, current) -> current)(双下划线)。
assylias 2014年

2
从技术上讲,它可能不适用于任何流。您所指向的文档以及用于遵守相遇顺序的文档Stream.reduce(BinaryOperator<T>)都没有提及reduce,即使流是有序的,终端操作也可以忽略相遇顺序。顺便说一句,“可交换的”一词在Stream javadocs中没有出现,因此它的缺失并不能告诉我们太多。
Aleksandr Dubinsky 2014年

2
@AleksandrDubinsky:确实,文档没有提到可交换的,因为它与reduce操作无关。重要的部分是:“ [..]正确构造的归约运算本质上是可并行化的,只要用于处理元素的函数是关联的[..]即可。”
nosid 2014年

2
@Aleksandr Dubinsky:当然,这不是“规格的理论问题”。它是reduce((a,b)->b)获取(最后一个有序流的)最后一个元素的正确解决方案之间的区别。Brian Goetz的声明指出了这一点,API文档进一步指出这reduce("", String::concat)是一个效率低下但正确的字符串连接解决方​​案,这意味着维护相遇顺序。意图是众所周知的,文档必须跟上。
Holger

42

如果您有一个Collection(或更通用的Iterable),则可以使用Google Guava的

Iterables.getLast(myIterable)

作为方便的班轮。


1
您可以轻松地将流转换为可迭代的流:Iterables.getLast(() -> data.careas.stream().filter(c -> c.bbox.orientationHorizontal).iterator())
shmosel

10

一个班轮(无需流;):

Object lastElement = list.get(list.size()-1);

30
如果list为空,则此代码将抛出ArrayIndexOutOfBoundsException

8

番石榴有专门针对这种情况的方法:

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

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

文档

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

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


1
@ZhekaKozlov有点... Holger 在这里
Eugene

0

如果需要获取最后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();

另一种选择是使用将身份用作队列的reduce操作。

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

-1

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

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

使用起来超级简单。


注意:处理庞大的集合(数百万个条目)时,您不应该依赖流的“跳过”,因为“跳过”是通过迭代所有元素直到达到第N个数字来实现的。试过了 与简单的按索引获取操作相比,它对性能感到非常失望。
java.is.for.desktop

1
同样如果列表为空,它将抛出ArrayIndexOutOfBoundsException
JindraVysocký19/
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.