My requirement is to find out all the matching objects with given criteria.
So what I did was created a Custom predicate and passed the matching criteria and the source IDs that should be matched with the Objects. If the condition is matching, then I am internally populating a map to hold the details.
And when the stream is completed, this map will have all the matching criteria details.
So I use this map for later computation.
Most of the time it works, but starts failing randomly.
My Custom predicate is -
public class IdentifierMapPredicate<T extends Identifiable, V> implements Predicate<T> {
private static Logger logger = Logger
.getLogger();
private Collection<V> sourceIds;
private BiPredicate<T, V> matchingCondition;
private Map<V, Collection<Identifier>> identifierMap;
public Map<V, Collection<Identifier>> getIdentifierMap() {
return identifierMap;
}
public IdentifierMapPredicate(Collection<V> sourceIds,
BiPredicate<T, V> matchingCondition) {
this.sourceIds = sourceIds;
this.matchingCondition = matchingCondition;
identifierMap = new HashMap<>(sourceIds.size());
}
#Override
public boolean test(T t) {
for (V id : sourceIds) {
if (matchingCondition.test(t, id)) {
//logger.debug("t :{}, id :{}", t.getIdentifier(), id);
Collection<Identifier> ids = identifierMap.get(id);
if(ids == null){
ids = new HashSet<>();
identifierMap.put(id, ids);
}
ids.add(t.getIdentifier());
logger.debug("identifierMap :{}", identifierMap);
return true;
}
}
return false;
}
}
I added log statements when the criteria is matched and we update the map, sometime the map has only 1 element (even though I am expecting 2) or sometimes its 0.
2019-11-24 09:31:52.574 PST DEBUG ForkJoinPool.commonPool-worker-5 IdentifierMapPredicate:52 - identifierMap :{}
2019-11-24 09:31:52.574 PST DEBUG ForkJoinPool.commonPool-worker-7 IdentifierMapPredicate:52 -identifierMap :{}
Is there anything wrong with the above implementation?
Should changing it to ConcurrentHashMap will help?
Most of the time it works, but starts failing randomly
Yeah, classical race condition.
You're using IdentifierMapPredicate in a multi-threaded context. If you access IdentifierMapPredicate.test concurrently, threads will use also identifierMap simulaneously, which is not thread safe.
It looks like a ConcurrentHashMap will solve the issue. Alternatively you could modify method test with the synchronized keyword, but that will give you less throughput/more locking I'd assume. But on the other hand also less headache, its a trade-off where I'd gladly accept less headache if there aren't tight performance requirements. Just my personal preference.
Btw. nowadays you can use Map.computeIfAbsent to create new entries instead of
Collection<Identifier> ids = identifierMap.get(id);
if(ids == null){
ids = new HashSet<>();
identifierMap.put(id, ids);
}
I have the following situation, where the lambda expression i used to
replace a working for loop does not work. Have no clue as to why this
fails
public class Abc implements IAbc {
// some fields
...
// field i'm interested in
#Inject #Any
private Instance<HandlerInterface> handlers;
// more members
...
// method i'm interested in
#Override
public boolean hasHandler(List<Order> orders) {
for (Order anOrder : orders) {
for (HandlerInterface aHandler : handlers) {
// following canHandler() is implemented by each
// handler that implements the HandlerInterface
if(aHandler.canHandle(anOrder)) {
return true;
}
}
return false;
}
// rest of the class content
.....
}
So I was actually trying to replace the above code, within the method,
with Lambdas (to which i'm new). The following was my replacement code
public boolean hasHandler(List<Order> orders) {
return orders.stream().anyMatch(order ->
Stream.of(handlers).map(Provider::get).anyMatch(handler ->
handler.canHandle(order)));
}
The above lambda expression fails at handler.canHandle with
AmbiguousResolutionException. I can't find out why it works with the
for loop and not with stream. Im sure im doing something wrong here -
but have no clue as to what. Any help on this is greatly appreciated.
I'm assuming Instance<HandlerInterface> implements Iterable<HandlerInterface> (or you wouldn't be able to use it in the enhanced for loop).
This means you can create a Stream of the elements of this Iterable by calling StreamSupport.stream(handlers.spliterator(), false);
So now, your method can be converted to use Streams as follows:
public boolean hasHandler(List<Order> orders) {
return orders.stream()
.anyMatch(o -> StreamSupport.stream(handlers.spliterator(), false)
.anyMatch(h -> h.canHandle(o)));
}
Note: I removed the .map(Provider::get) step, since it has no corresponding step in your original nested loop code.
In such cases create predicates starting from the innermost condition
First predicate is
if(aHandler.canHandle(anOrder)) {
return true;
}
You can write this in lambda as
private static Predicate<? super HandlerInterface> p1(Order o) {
return h -> h.canHandle(o);
}
Second(though is not very intuitive) but it is you want to check that for your order any handler that matches predicate p1, hence write that as
private static Predicate<? super Order> p2(List<HandlerInterface> handlers){
return o -> handlers.stream().anyMatch(p1(o));
}
Hence complete solution becomes
orders.stream().anyMatch(p2(handlers));
When written inline and removing braces because of single-line predicates you can re-write this as
orders.stream().anyMatch(o -> handlers.stream().anyMatch(h -> h.canHandle(o)));
To get familliar with the stream api, I tried to code a quite simple pattern.
Problem: Having a text file containing not nested blocks of text. All blocks are identified by start/endpatterns (e.g. <start> and <stop>. The content of a block isn't syntactically distinguishable from the noise between the blocks. Therefore it is impossible, to work with simple (stateless) lambdas.
I was just able to implement something ugly like:
Files.lines(path).collect(new MySequentialParseAndProsessEachLineCollector<>());
To be honest, this is not what I want.
Im looking for a mapper something like:
Files.lines(path).map(MyMapAllLinesOfBlockToBuckets()).parallelStream().collect(new MyProcessOneBucketCollector<>());
is there a good way to extract chunks of data from a java 8 stream seems to contain a skeleton of a solution. Unfortunatly, I'm to stubid to translate that to my problem. ;-)
Any hints?
Here is a solution which can be used for converting a Stream<String>, each element representing a line, to a Stream<List<String>>, each element representing a chunk found using a specified delimiter:
public class ChunkSpliterator implements Spliterator<List<String>> {
private final Spliterator<String> source;
private final Predicate<String> start, end;
private final Consumer<String> getChunk;
private List<String> current;
ChunkSpliterator(Spliterator<String> lineSpliterator,
Predicate<String> chunkStart, Predicate<String> chunkEnd) {
source=lineSpliterator;
start=chunkStart;
end=chunkEnd;
getChunk=s -> {
if(current!=null) current.add(s);
else if(start.test(s)) current=new ArrayList<>();
};
}
public boolean tryAdvance(Consumer<? super List<String>> action) {
while(current==null || current.isEmpty()
|| !end.test(current.get(current.size()-1)))
if(!source.tryAdvance(getChunk)) return false;
current.remove(current.size()-1);
action.accept(current);
current=null;
return true;
}
public Spliterator<List<String>> trySplit() {
return null;
}
public long estimateSize() {
return Long.MAX_VALUE;
}
public int characteristics() {
return ORDERED|NONNULL;
}
public static Stream<List<String>> toChunks(Stream<String> lines,
Predicate<String> chunkStart, Predicate<String> chunkEnd,
boolean parallel) {
return StreamSupport.stream(
new ChunkSpliterator(lines.spliterator(), chunkStart, chunkEnd),
parallel);
}
}
The lines matching the predicates are not included in the chunk; it would be easy to change this behavior, if desired.
It can be used like this:
ChunkSpliterator.toChunks( Files.lines(Paths.get(myFile)),
Pattern.compile("^<start>$").asPredicate(),
Pattern.compile("^<stop>$").asPredicate(),
true )
.collect(new MyProcessOneBucketCollector<>())
The patterns are specifying as ^word$ to require the entire line to consist of the word only; without these anchors, lines containing the pattern can start and end a chunk. The nature of the source stream does not allow parallelism when creating the chunks, so when chaining with an immediate collection operation the parallelism for the entire operation is rather limited. It depends on the MyProcessOneBucketCollector if there can be any parallelism at all.
If your final result does not depend on the order of occurrences of the buckets in the source file, it is strongly recommended that either your collector reports itself to be UNORDERED or you insert an unordered() in the stream’s method chains before the collect.
Lets say I'm observing an observable in a very specific way.
resultObservable = anotherObservable.filter(~Filter code~).take(15);
I'd like to create a custom operator that combines two predefined operators like filter, and take. Such that it behaves like
resultObservable = anotherObservable.lift(new FilterAndTake(15));
or...
resultObservable = anotherObservable.FilterAndTake(15);
So far Im comfortable with writing a very specific operator that can do this. And I can lift that operator.
But, given my currently limited knowledge of rx java, this would involve re-writing the take and filter functionality every time I need to use it in a custom operator.
Doing this is fine, But I'd rather re-use pre-existing operators that are maintained by an open source community, as well as recycle operators I've created.
Something also tells me I lack adequate knowledge about operators and subscribers.
Can someone recommend tutorials that aren't rx-java documentation?
I say this because, while docs explain general concepts, it isolates the concepts and general contexts of their functionality leaving no examples to inspire more robust applications of RX java.
So essentailly
I'm trying to encapsulate custom-dataflows into representative operators. Does this functionality exist?
I'm not aware of some special function (or sugar) that composes Operator objects. But you can simply create a new Operator to compose existing Operators.
Here's a working example of the FilterAndTake Operator:
public class FilterAndTake<T> implements Observable.Operator<T, T> {
private OperatorFilter<T> filter;
private OperatorTake<T> take;
public FilterAndTake(Func1<? super T, Boolean> predicate, int n) {
this.filter = new OperatorFilter<T>(predicate);
this.take = new OperatorTake<T>(n);
}
#Override
public Subscriber<? super T> call(final Subscriber<? super T> child) {
return filter.call(take.call(child));
}
}
And then you can use it as follows:
public static void main(String[] args) {
Observable<Integer> xs = Observable.range(1, 8);
Func1<Integer, Boolean> predicate = new Func1<Integer, Boolean>() {
#Override
public Boolean call(Integer x) {
return x % 2 == 0;
}
};
Action1<Integer> action = new Action1<Integer>() {
#Override
public void call(Integer x) {
System.out.println("> " + x);
}
};
xs.lift(new FilterAndTake<Integer>(predicate, 2)).subscribe(action);
}
A bit late to the party, but that's why compose exists:
Observable
.from(....)
.flatMap(... -> ....)
.compose(filterAndTake(15))
.subscribe(...)
public <T> Transformer<T,T> flterAndTake(int num) {
return source -> source
.filter(~Filter code~)
.take(num);
}
The new Java 8 stream framework and friends make for some very concise Java code, but I have come across a seemingly-simple situation that is tricky to do concisely.
Consider a List<Thing> things and method Optional<Other> resolve(Thing thing). I want to map the Things to Optional<Other>s and get the first Other.
The obvious solution would be to use things.stream().flatMap(this::resolve).findFirst(), but flatMap requires that you return a stream, and Optional doesn't have a stream() method (or is it a Collection or provide a method to convert it to or view it as a Collection).
The best I can come up with is this:
things.stream()
.map(this::resolve)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst();
But that seems awfully long-winded for what seems like a very common case.
Anyone have a better idea?
Java 9
Optional.stream has been added to JDK 9. This enables you to do the following, without the need of any helper method:
Optional<Other> result =
things.stream()
.map(this::resolve)
.flatMap(Optional::stream)
.findFirst();
Java 8
Yes, this was a small hole in the API, in that it's somewhat inconvenient to turn an Optional<T> into a zero-or-one length Stream<T>. You could do this:
Optional<Other> result =
things.stream()
.map(this::resolve)
.flatMap(o -> o.isPresent() ? Stream.of(o.get()) : Stream.empty())
.findFirst();
Having the ternary operator inside the flatMap is a bit cumbersome, though, so it might be better to write a little helper function to do this:
/**
* Turns an Optional<T> into a Stream<T> of length zero or one depending upon
* whether a value is present.
*/
static <T> Stream<T> streamopt(Optional<T> opt) {
if (opt.isPresent())
return Stream.of(opt.get());
else
return Stream.empty();
}
Optional<Other> result =
things.stream()
.flatMap(t -> streamopt(resolve(t)))
.findFirst();
Here, I've inlined the call to resolve() instead of having a separate map() operation, but this is a matter of taste.
I'm adding this second answer based on a proposed edit by user srborlongan to my other answer. I think the technique proposed was interesting, but it wasn't really suitable as an edit to my answer. Others agreed and the proposed edit was voted down. (I wasn't one of the voters.) The technique has merit, though. It would have been best if srborlongan had posted his/her own answer. This hasn't happened yet, and I didn't want the technique to be lost in the mists of the StackOverflow rejected edit history, so I decided to surface it as a separate answer myself.
Basically the technique is to use some of the Optional methods in a clever way to avoid having to use a ternary operator (? :) or an if/else statement.
My inline example would be rewritten this way:
Optional<Other> result =
things.stream()
.map(this::resolve)
.flatMap(o -> o.map(Stream::of).orElseGet(Stream::empty))
.findFirst();
An my example that uses a helper method would be rewritten this way:
/**
* Turns an Optional<T> into a Stream<T> of length zero or one depending upon
* whether a value is present.
*/
static <T> Stream<T> streamopt(Optional<T> opt) {
return opt.map(Stream::of)
.orElseGet(Stream::empty);
}
Optional<Other> result =
things.stream()
.flatMap(t -> streamopt(resolve(t)))
.findFirst();
COMMENTARY
Let's compare the original vs modified versions directly:
// original
.flatMap(o -> o.isPresent() ? Stream.of(o.get()) : Stream.empty())
// modified
.flatMap(o -> o.map(Stream::of).orElseGet(Stream::empty))
The original is a straightforward if workmanlike approach: we get an Optional<Other>; if it has a value, we return a stream containing that value, and if it has no value, we return an empty stream. Pretty simple and easy to explain.
The modification is clever and has the advantage that it avoids conditionals. (I know that some people dislike the ternary operator. If misused it can indeed make code hard to understand.) However, sometimes things can be too clever. The modified code also starts off with an Optional<Other>. Then it calls Optional.map which is defined as follows:
If a value is present, apply the provided mapping function to it, and if the result is non-null, return an Optional describing the result. Otherwise return an empty Optional.
The map(Stream::of) call returns an Optional<Stream<Other>>. If a value was present in the input Optional, the returned Optional contains a Stream that contains the single Other result. But if the value was not present, the result is an empty Optional.
Next, the call to orElseGet(Stream::empty) returns a value of type Stream<Other>. If its input value is present, it gets the value, which is the single-element Stream<Other>. Otherwise (if the input value is absent) it returns an empty Stream<Other>. So the result is correct, the same as the original conditional code.
In the comments discussing on my answer, regarding the rejected edit, I had described this technique as "more concise but also more obscure". I stand by this. It took me a while to figure out what it was doing, and it also took me a while to write up the above description of what it was doing. The key subtlety is the transformation from Optional<Other> to Optional<Stream<Other>>. Once you grok this it makes sense, but it wasn't obvious to me.
I'll acknowledge, though, that things that are initially obscure can become idiomatic over time. It might be that this technique ends up being the best way in practice, at least until Optional.stream gets added (if it ever does).
UPDATE: Optional.stream has been added to JDK 9.
You cannot do it more concise as you are already doing.
You claim that you do not want .filter(Optional::isPresent) and .map(Optional::get).
This has been resolved by the method #StuartMarks describes, however as a result you now map it to an Optional<T>, so now you need to use .flatMap(this::streamopt) and a get() in the end.
So it still consists of two statements and you can now get exceptions with the new method! Because, what if every optional is empty? Then the findFirst() will return an empty optional and your get() will fail!
So what you have:
things.stream()
.map(this::resolve)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst();
is actually the best way to accomplish what you want, and that is you want to save the result as a T, not as an Optional<T>.
I took the liberty of creating a CustomOptional<T> class that wraps the Optional<T> and provides an extra method, flatStream(). Note that you cannot extend Optional<T>:
class CustomOptional<T> {
private final Optional<T> optional;
private CustomOptional() {
this.optional = Optional.empty();
}
private CustomOptional(final T value) {
this.optional = Optional.of(value);
}
private CustomOptional(final Optional<T> optional) {
this.optional = optional;
}
public Optional<T> getOptional() {
return optional;
}
public static <T> CustomOptional<T> empty() {
return new CustomOptional<>();
}
public static <T> CustomOptional<T> of(final T value) {
return new CustomOptional<>(value);
}
public static <T> CustomOptional<T> ofNullable(final T value) {
return (value == null) ? empty() : of(value);
}
public T get() {
return optional.get();
}
public boolean isPresent() {
return optional.isPresent();
}
public void ifPresent(final Consumer<? super T> consumer) {
optional.ifPresent(consumer);
}
public CustomOptional<T> filter(final Predicate<? super T> predicate) {
return new CustomOptional<>(optional.filter(predicate));
}
public <U> CustomOptional<U> map(final Function<? super T, ? extends U> mapper) {
return new CustomOptional<>(optional.map(mapper));
}
public <U> CustomOptional<U> flatMap(final Function<? super T, ? extends CustomOptional<U>> mapper) {
return new CustomOptional<>(optional.flatMap(mapper.andThen(cu -> cu.getOptional())));
}
public T orElse(final T other) {
return optional.orElse(other);
}
public T orElseGet(final Supplier<? extends T> other) {
return optional.orElseGet(other);
}
public <X extends Throwable> T orElseThrow(final Supplier<? extends X> exceptionSuppier) throws X {
return optional.orElseThrow(exceptionSuppier);
}
public Stream<T> flatStream() {
if (!optional.isPresent()) {
return Stream.empty();
}
return Stream.of(get());
}
public T getTOrNull() {
if (!optional.isPresent()) {
return null;
}
return get();
}
#Override
public boolean equals(final Object obj) {
return optional.equals(obj);
}
#Override
public int hashCode() {
return optional.hashCode();
}
#Override
public String toString() {
return optional.toString();
}
}
You will see that I added flatStream(), as here:
public Stream<T> flatStream() {
if (!optional.isPresent()) {
return Stream.empty();
}
return Stream.of(get());
}
Used as:
String result = Stream.of("a", "b", "c", "de", "fg", "hij")
.map(this::resolve)
.flatMap(CustomOptional::flatStream)
.findFirst()
.get();
You still will need to return a Stream<T> here, as you cannot return T, because if !optional.isPresent(), then T == null if you declare it such, but then your .flatMap(CustomOptional::flatStream) would attempt to add null to a stream and that is not possible.
As example:
public T getTOrNull() {
if (!optional.isPresent()) {
return null;
}
return get();
}
Used as:
String result = Stream.of("a", "b", "c", "de", "fg", "hij")
.map(this::resolve)
.map(CustomOptional::getTOrNull)
.findFirst()
.get();
Will now throw a NullPointerException inside the stream operations.
Conclusion
The method you used, is actually the best method.
A slightly shorter version using reduce:
things.stream()
.map(this::resolve)
.reduce(Optional.empty(), (a, b) -> a.isPresent() ? a : b );
You could also move the reduce function to a static utility method and then it becomes:
.reduce(Optional.empty(), Util::firstPresent );
As my previous answer appeared not to be very popular, I will give this another go.
A short answer:
You are mostly on a right track. The shortest code to get to your desired output I could come up with is this:
things.stream()
.map(this::resolve)
.filter(Optional::isPresent)
.findFirst()
.flatMap( Function.identity() );
This will fit all your requirements:
It will find first response that resolves to a nonempty Optional<Result>
It calls this::resolve lazily as needed
this::resolve will not be called after first non-empty result
It will return Optional<Result>
Longer answer
The only modification compared to OP initial version was that I removed .map(Optional::get) before call to .findFirst() and added .flatMap(o -> o) as the last call in the chain.
This has a nice effect of getting rid of the double-Optional, whenever stream finds an actual result.
You can't really go any shorter than this in Java.
The alternative snippet of code using the more conventional for loop technique is going to be about same number of lines of code and have more or less same order and number of operations you need to perform:
Calling this.resolve,
filtering based on Optional.isPresent
returning the result and
some way of dealing with negative result (when nothing was found)
Just to prove that my solution works as advertised, I wrote a small test program:
public class StackOverflow {
public static void main( String... args ) {
try {
final int integer = Stream.of( args )
.peek( s -> System.out.println( "Looking at " + s ) )
.map( StackOverflow::resolve )
.filter( Optional::isPresent )
.findFirst()
.flatMap( o -> o )
.orElseThrow( NoSuchElementException::new )
.intValue();
System.out.println( "First integer found is " + integer );
}
catch ( NoSuchElementException e ) {
System.out.println( "No integers provided!" );
}
}
private static Optional<Integer> resolve( String string ) {
try {
return Optional.of( Integer.valueOf( string ) );
}
catch ( NumberFormatException e )
{
System.out.println( '"' + string + '"' + " is not an integer");
return Optional.empty();
}
}
}
(It does have few extra lines for debugging and verifying that only as many calls to resolve as needed...)
Executing this on a command line, I got the following results:
$ java StackOferflow a b 3 c 4
Looking at a
"a" is not an integer
Looking at b
"b" is not an integer
Looking at 3
First integer found is 3
Late to the party, but what about
things.stream()
.map(this::resolve)
.filter(Optional::isPresent)
.findFirst().get();
You can get rid of the last get() if you create a util method to convert optional to stream manually:
things.stream()
.map(this::resolve)
.flatMap(Util::optionalToStream)
.findFirst();
If you return stream right away from your resolve function, you save one more line.
I'd like to promote factory methods for creating helpers for functional APIs:
Optional<R> result = things.stream()
.flatMap(streamopt(this::resolve))
.findFirst();
The factory method:
<T, R> Function<T, Stream<R>> streamopt(Function<T, Optional<R>> f) {
return f.andThen(Optional::stream); // or the J8 alternative:
// return t -> f.apply(t).map(Stream::of).orElseGet(Stream::empty);
}
Reasoning:
As with method references in general, compared to lambda expressions, you can't accidentaly capture a variable from the accessible scope, like:
t -> streamopt(resolve(o))
It's composable, you can e.g. call Function::andThen on the factory method result:
streamopt(this::resolve).andThen(...)
Whereas in the case of a lambda, you'd need to cast it first:
((Function<T, Stream<R>>) t -> streamopt(resolve(t))).andThen(...)
If you're stuck with Java 8 but have access to Guava 21.0 or newer, you can use Streams.stream to convert an optional into a stream.
Thus, given
import com.google.common.collect.Streams;
you can write
Optional<Other> result =
things.stream()
.map(this::resolve)
.flatMap(Streams::stream)
.findFirst();
If you don't mind to use a third party library you may use Javaslang. It is like Scala, but implemented in Java.
It comes with a complete immutable collection library that is very similar to that known from Scala. These collections replace Java's collections and Java 8's Stream. It also has its own implementation of Option.
import javaslang.collection.Stream;
import javaslang.control.Option;
Stream<Option<String>> options = Stream.of(Option.some("foo"), Option.none(), Option.some("bar"));
// = Stream("foo", "bar")
Stream<String> strings = options.flatMap(o -> o);
Here is a solution for the example of the initial question:
import javaslang.collection.Stream;
import javaslang.control.Option;
public class Test {
void run() {
// = Stream(Thing(1), Thing(2), Thing(3))
Stream<Thing> things = Stream.of(new Thing(1), new Thing(2), new Thing(3));
// = Some(Other(2))
Option<Other> others = things.flatMap(this::resolve).headOption();
}
Option<Other> resolve(Thing thing) {
Other other = (thing.i % 2 == 0) ? new Other(i + "") : null;
return Option.of(other);
}
}
class Thing {
final int i;
Thing(int i) { this.i = i; }
public String toString() { return "Thing(" + i + ")"; }
}
class Other {
final String s;
Other(String s) { this.s = s; }
public String toString() { return "Other(" + s + ")"; }
}
Disclaimer: I'm the creator of Javaslang.
Null is supported by the Stream provided My library abacus-common. Here is code:
Stream.of(things).map(e -> resolve(e).orNull()).skipNull().first();
What about that?
private static List<String> extractString(List<Optional<String>> list) {
List<String> result = new ArrayList<>();
list.forEach(element -> element.ifPresent(result::add));
return result;
}
https://stackoverflow.com/a/58281000/3477539
Most likely You are doing it wrong.
Java 8 Optional is not meant to be used in this manner. It is usually only reserved for terminal stream operations that may or may not return a value, like find for example.
In your case it might be better to first try to find a cheap way to filter out those items that are resolvable and then get the first item as an optional and resolve it as a last operation. Better yet - instead of filtering, find the first resolvable item and resolve it.
things.filter(Thing::isResolvable)
.findFirst()
.flatMap(this::resolve)
.get();
Rule of thumb is that you should strive to reduce number of items in the stream before you transform them to something else. YMMV of course.