Get last element of Stream/List in a one-liner
Solution 1
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();
Solution 2
If you have a Collection (or more general an Iterable) you can use Google Guava's
Iterables.getLast(myIterable)
as handy oneliner.
Solution 3
One liner (no need for stream;):
Object lastElement = list.isEmpty() ? null : list.get(list.size()-1);
Solution 4
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()
.
Solution 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.
skiwi
Studied Computer Science at the Eindhoven University of Technology. I used to be quite experienced in working with Java and have experience in PHP/MySQL/Javascript/jQuery/CSS/HTML aswell. Nowadays I work more often with C# and have some side projects in other languages. Feel free to check what I am working at on Github or to contact me at LinkedIn.
Updated on July 08, 2021Comments
-
skiwi almost 3 years
How can I get the last element of a stream or list in the following code?
Where
data.careas
is aList<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()
andlast()
from theList
interface, which is really a pain.
I do not see any argument for not providing a
first()
andlast()
method in theList
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.-
Aleksandr Dubinsky over 10 yearsGuava provides
Iterables.getLast
which takes Iterable but is optimized to work withList
. A pet peeve is that it doesn't havegetFirst
. TheStream
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<T,Boolean> predicate)
, even though it supports infinite Enumerables too. -
fasth about 9 years@AleksandrDubinsky upvoted, but one note for readers.
Stream
API is not fully comparable toLINQ
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 :) -
quantum almost 9 yearsFor a true one-liner, this thread may be of use.
- It seems I cannot obtain it directly from a
-
skiwi over 10 yearsNice 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. -
assylias over 10 years@skiwi you can use any legal variable name, for example:
.reduce(($, current) -> current)
or.reduce((__, current) -> current)
(double underscore). -
Aleksandr Dubinsky over 10 yearsI doubt this will work for parallel streams, even if they're ordered.
-
nosid over 10 years@AleksandrDubinsky: Why shouldn't it work for parallel streams? Take a look at the documentation: Reduction Operations.
-
Aleksandr Dubinsky over 10 yearsTechnically, it may not work for any streams. The documentation that you point to, as well as for
Stream.reduce(BinaryOperator<T>)
makes no mention ifreduce
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. -
nosid over 10 years@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 [..]."
-
Aleksandr Dubinsky over 10 yearsThe 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 thatreduce
does not obey ordering, and that a non-commutative function will produce undefined results. -
Aleksandr Dubinsky over 10 yearsThat said, the current implementation (from looking at
java.util.stream.ReduceOps
) seems to obey encounter order. -
René over 9 yearsThe 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." -
FBB over 8 yearsI added an answer trying to address the concerns in these comments.
-
Holger about 8 years@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.
-
Aleksandr Dubinsky about 8 years@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.
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. -
Holger about 8 years@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 clearly explains how that property relates to concurrent execution strategies and
reduce((a,b)->b)
is an acknowledged solution… -
Holger about 8 years@Aleksandr Dubinsky: don’t get me wrong, I have to admit that the documentation has room for improvements, similar to this issue, 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.
-
Aleksandr Dubinsky about 8 years@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. -
Holger about 8 years@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 states thatreduce("", 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. -
devssh about 6 yearsWhat 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
-
fernandohur almost 6 years@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. -
devssh almost 6 years@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()?
-
java.is.for.desktop over 5 yearsNote: 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.
-
Eugene almost 5 years@ZhekaKozlov sort of... Holger's showed some flaws with it here
-
Jindra Vysocký over 4 yearsalso if list is empty, it will throw
ArrayIndexOutOfBoundsException
-
shmosel over 4 yearsAnd you can easily convert a stream to an iterable:
Iterables.getLast(() -> data.careas.stream().filter(c -> c.bbox.orientationHorizontal).iterator())
-
MC Emperor almost 4 yearsFor readers for which it is not immediately clear where
get()
comes from:reduce(BinaryOperator)
returns anOptional
containing the element, or an empty optional if the stream is empty.get()
will in such case throw aNoSuchElementException
. -
Don Woodward over 3 yearsWhat 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