159

How can I get the last element of a stream or list in the following code?

Where data.careas is a List<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?

As you can see getting the first element, with a certain filter, is not hard.

However getting the last element in a one-liner is a real pain:

  • It seems I cannot obtain it directly from a Stream. (It would only make sense for finite streams)
  • It also seems that you cannot get things like first() and last() from the List interface, which is really a pain.

I do not see any argument for not providing a first() and last() method in the List interface, as the elements in there, are ordered, and moreover the size is known.

But as per the original answer: How to get the last element of a finite Stream?

Personally, this is the closest I could get:

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

However it does involve, using an indexOf on every element, which is most likely not you generally want as it can impair performance.

Naman
  • 27,789
  • 26
  • 218
  • 353
skiwi
  • 66,971
  • 31
  • 131
  • 216
  • 12
    Guava provides `Iterables.getLast` which takes Iterable but is optimized to work with `List`. A pet peeve is that it doesn't have `getFirst`. The `Stream` API in general is horribly anal, omitting lots of convenience methods. C#'s LINQ, by constrast, is happy to provide `.Last()` and even `.Last(Func predicate)`, even though it supports infinite Enumerables too. – Aleksandr Dubinsky Feb 05 '14 at 17:54
  • @AleksandrDubinsky upvoted, but one note for readers. `Stream` API is not fully comparable to `LINQ` since both done in a very different paradigm. It is not worse or better it is just different. And definitely some methods are absent not because oracle devs are incompetent or mean :) – fasth Mar 23 '15 at 02:49
  • 1
    For a true one-liner, [this thread](http://stackoverflow.com/questions/27547519/most-efficient-way-to-get-the-last-element-of-a-stream) may be of use. – quantum Jun 05 '15 at 16:50

10 Answers10

249

It is possible to get the last element with the method Stream::reduce. The following listing contains a minimal example for the general case:

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

This implementations works for all ordered streams (including streams created from Lists). For unordered streams it is for obvious reasons unspecified which element will be returned.

The implementation works for both sequential and parallel streams. That might be surprising at first glance, and unfortunately the documentation doesn't state it explicitly. However, it is an important feature of streams, and I try to clarify it:

  • The Javadoc for the method Stream::reduce states, that it "is not constrained to execute sequentially".
  • The Javadoc also requires that the "accumulator function must be an associative, non-interfering, stateless function for combining two values", which is obviously the case for the lambda expression (first, second) -> second.
  • The Javadoc for reduction operations states: "The streams classes have multiple forms of general reduction operations, called reduce() and collect() [..]" and "a properly constructed reduce operation is inherently parallelizable, so long as the function(s) used to process the elements are associative and stateless."

The documentation for the closely related Collectors is even more explicit: "To ensure that sequential and parallel executions produce equivalent results, the collector functions must satisfy an identity and an associativity constraints."


Back to the original question: The following code stores a reference to the last element in the variable last and throws an exception if the stream is empty. The complexity is linear in the length of the stream.

CArea last = data.careas
                 .stream()
                 .filter(c -> c.bbox.orientationHorizontal)
                 .reduce((first, second) -> second).get();
Naman
  • 27,789
  • 26
  • 218
  • 353
nosid
  • 48,932
  • 13
  • 112
  • 139
  • Nice one, thanks! Do you by the way know if it is possibly to omit a name (perhaps by using a `_` or similar) in cases where you do not need a parameter? So would be: `.reduce((_, current) -> current)` if only that aws valid syntax. – skiwi Jan 29 '14 at 20:18
  • 2
    @skiwi you can use any legal variable name, for example: `.reduce(($, current) -> current)` or `.reduce((__, current) -> current)` (double underscore). – assylias Jan 30 '14 at 10:52
  • I doubt this will work for parallel streams, even if they're ordered. – Aleksandr Dubinsky Feb 05 '14 at 17:58
  • @AleksandrDubinsky: Why shouldn't it work for parallel streams? Take a look at the documentation: [Reduction Operations](http://download.java.net/jdk8/docs/api/java/util/stream/package-summary.html#Reduction). – nosid Feb 05 '14 at 21:30
  • 2
    Technically, it may not work for any streams. The documentation that you point to, as well as for `Stream.reduce(BinaryOperator)` makes no mention if `reduce` obeys encounter order, and a terminal operation is free to ignore encounter order even if the stream is ordered. As an aside, the word "commutative" doesn't appear in the Stream javadocs, so its absence does not tell us much. – Aleksandr Dubinsky Feb 05 '14 at 22:06
  • 2
    @AleksandrDubinsky: Exactly, the documentation doesn't mention _commutative_, because it is not relevant for the _reduce_ operation. The important part is: "[..] A properly constructed reduce operation is inherently parallelizable, so long as the function(s) used to process the elements are associative [..]." – nosid Feb 05 '14 at 22:43
  • The documentation doesn't mention "commutative" *anywhere*, even though it's relevant to many functions. The terminology that it uses is "encounter order", and the `reduce` documentation makes no mention of it one way or the other. Therefore, we must assume that `reduce` does not obey ordering, and that a non-commutative function will produce undefined results. – Aleksandr Dubinsky Feb 06 '14 at 13:53
  • That said, the current implementation (from looking at `java.util.stream.ReduceOps`) seems to obey encounter order. – Aleksandr Dubinsky Feb 06 '14 at 14:14
  • The class-comment of `java.util.Stream` says "Note that if it is important that the elements for a given key appear in the order they appear in the source, then we cannot use a concurrent reduction, as ordering is one of the casualties of concurrent insertion. We would then be constrained to implement either a sequential reduction or a merge-based parallel reduction." – René Nov 27 '14 at 08:50
  • I added an answer trying to address the concerns in these comments. – FBB Jan 14 '16 at 12:30
  • @Aleksandr Dubinsky: there is no relevance of commutativity to *any* function in the Stream API. If you want to claim otherwise, name at least *one* for which it is. – Holger Apr 18 '16 at 13:28
  • @Holger Define "relevance." Anyway, to reiterate my point, which noone except @FBB seems to have appreciated, is that "orderness" of a Stream means nothing to a terminal operation, which is free to not declaring itself as preserving "encounter order." Search for this term in the [javadocs](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html). `reduce` happily leaves this aspect undefined. We must conclude, therefore, that conformant implementations are free to produce somewhat undefined results if the lambda is not commutative. Stream API sucks. All this drama over a trifle. – Aleksandr Dubinsky Apr 19 '16 at 14:15
  • @Aleksandr Dubinsky: I like how you interpret the absence of a statement as “the implementor is free to do something undocumented”. That contradicts the fact that every method which is allowed to ignore the encounter order has an explicit statement about it, but never mind. Note that [the documentation regarding Associativity](https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html#Associativity) clearly explains how that property relates to concurrent execution strategies and `reduce((a,b)->b)` is [an acknowledged solution](http://stackoverflow.com/a/27547525/2711488)… – Holger Apr 19 '16 at 14:37
  • @Aleksandr Dubinsky: don’t get me wrong, I have to admit that the documentation has room for improvements, similar to [this issue](http://stackoverflow.com/q/36711845/2711488), but if someone like the Java Language Architect involved in the development of that API acknowledges that it is the intended way it should work, I’ll consider the deficiencies of the documentation as exactly that, documentation flaws, rather than room for contradicting implementations. – Holger Apr 19 '16 at 14:42
  • @Holger Whether reduce does or does not respect encounter order is not just a theoretical question of the spec. It's hugely important, because when a terminal operation does not respect encounter order, the whole pipeline becomes unordered. For example, the operation `skip` goes crazy on unordered parallel streams and can **skip the last element** for a short stream. This answer is possibly/likely wrong in the general case. The fact this isn't defined in the javadoc or the spec is criminal. Stream API sucks. – Aleksandr Dubinsky Apr 19 '16 at 15:41
  • 2
    @Aleksandr Dubinsky: of course, it’s not a “theoretical question of the spec”. It makes the difference between `reduce((a,b)->b)` being a correct solution for getting the last element (of an ordered stream,of course) or not. The statement of Brian Goetz makes a point, further the [API documentation](https://docs.oracle.com/javase/8/docs/api/?java/util/stream/package-summary.html) states that `reduce("", String::concat)` is an inefficient but correct solution for string concatenation, which implies maintenance of the encounter order.The intention is well-known,the documentation has to catch up. – Holger Apr 19 '16 at 16:02
  • What is the efficiency of this approach? Linear complexity is pretty bad. It seems to be going over the list reducing till it reaches the last element. What if the list is really big? Wouldn't it be faster if it was hashed and there was an inbuilt method to just get the last element – devssh Apr 02 '18 at 15:30
  • @devssh Streams are lazy constructs, you cannot assume that you have all elements in the stream in memory at all times so you can't do better than `O(n)` in general. – fernandohur Jun 01 '18 at 13:18
  • @fernandohur Yes I agree, but this feels like a mapreduce to get the last element while all it needs is to access the last element by memory. Looks like the job of an array but arrays are inherently imperative instead of declarative. Fits the criteria for an inbuilt method called last()? – devssh Jun 05 '18 at 10:13
  • For readers for which it is not immediately clear where `get()` comes from: `reduce(BinaryOperator)` returns an `Optional` containing the element, or an empty optional if the stream is empty. `get()` will in such case throw a `NoSuchElementException`. – MC Emperor Jun 20 '20 at 13:18
54

If you have a Collection (or more general an Iterable) you can use Google Guava's

Iterables.getLast(myIterable)

as handy oneliner.

Peti
  • 1,670
  • 1
  • 20
  • 25
  • 1
    And you can easily convert a stream to an iterable: `Iterables.getLast(() -> data.careas.stream().filter(c -> c.bbox.orientationHorizontal).iterator())` – shmosel Feb 12 '20 at 20:55
15

One liner (no need for stream;):

Object lastElement = list.isEmpty() ? null : list.get(list.size()-1);
nimo23
  • 5,170
  • 10
  • 46
  • 75
14

Guava has dedicated method for this case:

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

It's equivalent to stream.reduce((a, b) -> b) but creators claim it has much better performance.

From documentation:

This method's runtime will be between O(log n) and O(n), performing better on efficiently splittable streams.

It's worth to mention that if stream is unordered this method behaves like findAny().

k13i
  • 4,011
  • 3
  • 35
  • 63
5
list.stream().sorted(Comparator.comparing(obj::getSequence).reversed()).findFirst().get();

reverse the order and get the first element from the list. here object has sequence number, Comparator provides multiple functionalities can be used as per logic.

elesg
  • 63
  • 1
  • 5
  • 1
    You can use max() like this : list.stream().max(Comparator.comparing(obj::getSequence)).get(); – Jeff Feb 01 '23 at 09:20
1

Another way to get the last element is by using sort.

    Optional<CArea> num=data.careas.stream().sorted((a,b)->-1).findFirst();
0

You can also use skip() function as below...

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

it's super simple to use.

Parag Vaidya
  • 81
  • 1
  • 7
  • 3
    Note: you shouldn't rely on stream's "skip" when dealing with huge collections (millions of entries), because "skip" is implemented by iterating through all elements until the Nth number is reached. Tried it. Was very disappointed by the performance, compared to a simple get-by-index operation. – java.is.for.desktop Dec 01 '18 at 09:11
  • 3
    also if list is empty, it will throw `ArrayIndexOutOfBoundsException` – Jindra Vysocký Dec 13 '19 at 17:53
  • What if its not a list ? Get wont be available , I had a requirement to take a set and stream it and then the above solution was working. There is no option in linkedhashset to get last or nth element from back without iterating thru elements. I upvoted this to save the author from drowning :D – Don Woodward Nov 01 '20 at 01:08
0

One more approach. Pair will have first and last elements:

    List<Object> pair = new ArrayList<>();
    dataStream.ForEach(o -> {
        if (pair.size() == 0) {
            pair.add(o);
            pair.add(o);
        }
        pair.set(1, o);
    });
spyr01d
  • 9
  • 2
0

Java 21 is adding a getLast() method to the List interface, which returns the last element of the list.

CArea last = data.careas.stream()
                 .filter(c -> c.bbox.orientationHorizontal)
                 .collect(Collectors.toList())
                 .getLast();

Also being added is a getFirst() method, which addresses your point about not having this functionality on the List interface being a pain.

M. Justin
  • 14,487
  • 7
  • 91
  • 130
-1

If you need to get the last N number of elements. Closure can be used. The below code maintains an external queue of fixed size until, the stream reaches the end.

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

Another option could be to use reduce operation using identity as a Queue.

    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);
Himanshu Ahire
  • 677
  • 5
  • 18