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();
Answer from nosid on Stack OverflowIt 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();
If you have a Collection (or more general an Iterable) you can use Google Guava's
Iterables.getLast(myIterable)
as handy oneliner.
Do a reduction that simply returns the current value:
Stream<T> stream;
T last = stream.reduce((a, b) -> b).orElse(null);
This heavily depends on the nature of the Stream. Keep in mind that “simple” doesn’t necessarily mean “efficient”. If you suspect the stream to be very large, carrying heavy operations or having a source which knows the size in advance, the following might be substantially more efficient than the simple solution:
static <T> T getLast(Stream<T> stream) {
Spliterator<T> sp=stream.spliterator();
if(sp.hasCharacteristics(Spliterator.SIZED|Spliterator.SUBSIZED)) {
for(;;) {
Spliterator<T> part=sp.trySplit();
if(part==null) break;
if(sp.getExactSizeIfKnown()==0) {
sp=part;
break;
}
}
}
T value=null;
for(Iterator<T> it=recursive(sp); it.hasNext(); )
value=it.next();
return value;
}
private static <T> Iterator<T> recursive(Spliterator<T> sp) {
Spliterator<T> prev=sp.trySplit();
if(prev==null) return Spliterators.iterator(sp);
Iterator<T> it=recursive(sp);
if(it!=null && it.hasNext()) return it;
return recursive(prev);
}
You may illustrate the difference with the following example:
String s=getLast(
IntStream.range(0, 10_000_000).mapToObj(i-> {
System.out.println("potential heavy operation on "+i);
return String.valueOf(i);
}).parallel()
);
System.out.println(s);
It will print:
potential heavy operation on 9999999
9999999
In other words, it did not perform the operation on the first 9999999 elements but only on the last one.
You could use skip:
elements.stream().skip(elements.size() - 2)
From the API:
Returns a stream consisting of the remaining elements of this stream after discarding the first n elements of the stream. If this stream contains fewer than n elements then an empty stream will be returned.
Probably useless example:
// a list made of a, b, c and d
List<String> l = Arrays.asList("a", "b", "c", "d");
// prints c and d
l.stream().skip(l.size() - 2).forEach(System.out::println);
Probably useless note:
As mentioned by a few, this only works if you have a size to work with, i.e. if you're streaming from a collection.
Quoting Nicolas, a stream doesn't have a size.
You could do a bit of inlining of your original approach, which I think shortens it up nicely, and you don't even have to use a stream:
String[] a = fullDomain.split("\\.");
return String.join(".", Arrays.asList(a)
.subList(Math.max(0, a.length-2), a.length));
If you really want to use a stream, you can use the array-subrange stream source:
String[] a = fullDomain.split("\\.");
return Arrays.stream(a, Math.max(0, a.length-2), a.length)
.collect(Collectors.joining("."));
If all you have is a stream, whose size you don't have in advance, I'd just dump the elements into an ArrayDeque:
final int N = 2;
Stream<String> str = ... ;
Deque<String> deque = new ArrayDeque<>(N);
str.forEachOrdered(s -> {
if (deque.size() == N) deque.removeFirst();
deque.addLast(s);
});
return String.join(".", deque);
Of course, this isn't as general as writing a collector, but for simple cases it's probably just fine.
Use Stream.skip()
Returns a stream consisting of the remaining elements of this stream after discarding the first n elements of the stream. If this stream contains fewer than n elements then an empty stream will be returned.
all.stream().skip(Math.max(0, all.size() - n)).forEach(doSomething);
A custom collector can be written like this:
public static <T> Collector<T, ?, List<T>> lastN(int n) {
return Collector.<T, Deque<T>, List<T>>of(ArrayDeque::new, (acc, t) -> {
if(acc.size() == n)
acc.pollFirst();
acc.add(t);
}, (acc1, acc2) -> {
while(acc2.size() < n && !acc1.isEmpty()) {
acc2.addFirst(acc1.pollLast());
}
return acc2;
}, ArrayList::new);
}
And use it like this:
List<String> lastTen = input.stream().collect(lastN(10));