Terminal operation to evaluate intermediate operation - java

Let says i have a list of strings and i want to use those strings as input to a fluent builder.
List<String> scripts;
//initialize list
ScriptRunnerBuilder scriptRunnerBuilder = new ScriptRunnerBuilder();
BiFunction<String,ScriptRunnerBuilder,ScriptRunnerBuilder> addScript =
(script,builder) -> builer.addScript(script);
scriptRunnerBuilder = scripts.stream.map(script ->
addScript.apply(script,scriptRunnerBuilder)).......
scriptRunnerBuilder.build();
which terminal operation can i use so that the addScript function gets called for all elements in the list?
The issue is that the ScriptRunnerBuilder is immutable whereby ScriptRunnerBuilder.addScript() returns a new ScriptRunnerBuilder object rather than modifying existing – so i can't just us a foreach.
My intentions are to carry the result of the addScript() call and use that as input for the next element in the stream

In simplest way this should:
// create your builder
ScriptRunnerBuilder builder = new ScriptRunnerBuilder();
// add all scripts
scripts.forEach(script-> builder.addScript(script))
build results
scriptRunnerBuilder.build();
Because builder aggregates all data, and you have created it outside forEach lambda, you can access it directly. This will lead to less code and same result.
Or as #Holger suggested:
scripts.forEach(builder::addScript);

Use forEach instead of map and don't assign the result of the stream anymore
scripts.forEach(script -> addScript.apply(script,scriptRunnerBuilder));

i could use reduce operation but that is unnecessary as we are not combining results
Combining is exactly what you are doing.
You combine all scripts from List<String> to ScriptRunnerBuilder aren't you?
I agree that the #Beri's solution without stream probably is the simplest. But also there is a way with reduce(identity, accumulator, combiner) method where you don't need to create ScriptRunnerBuilder before:
ScriptRunnerBuilder builder = scripts.stream()
.reduce(new ScriptRunnerBuilder(), ScriptRunnerBuilder::addScript, (b1, b2) -> b1);
See more: Why is a combiner needed for reduce method that converts type in java 8
Update To not to rely on the fact that combiner not being invoked for sequential stream and to make it works with parallel one you have to implement the real combiner.
If you could add an overrided method addScript(ScriptRunnerBuilder otherBuilder) then the reduce will look like:
.reduce(new ScriptRunnerBuilder(), ScriptRunnerBuilder::addScript,
ScriptRunnerBuilder::addScript)

Related

Java stream, filter, and then do something with resulting collection

Goal
final List<T> listOfThings = ...;
listOfThings.stream()
.filter(...) // returns a Stream<T>
.then(filteredListOfThings -> {
// How do I get here so I can work on the newly filtered collection
// in a fluent way w/out collecting the result to a variable?
// For example, if I need to process the elements but don't
// care about them in their current form outside this chain.
});
Problem
In English, given a list of something, I'd like to stream the list, filter it, and then operate on the entire filtered result. I can accomplish this with optional but it's not clean IMO:
final List<T> listOfThings = ...;
Optional
.of(listOfThings.stream()
.filter(...) // returns a Stream<T>
.collect(Collectors.toList()))
.map(filteredListOfThings -> {
// I'm here, now, but would like to not have to wrap it in an Optional<T>
});
It'd be cool if there was a then or similar method on a Stream<T> which returns Stream<T> to allow for further chaining, which allows me to work with the entire set of results within the lambda without declaring an outside variable.
Don't make it more complicated than it needs to be.
Assign the result of the collect to a variable, then operate on that variable:
List<T> filteredListOfThings = ... .collect(toList());
// Now use filteredListOfThings.
filteredListOfThings will always have a value, even if it's the empty list, so there's no point in using Optional.
And there's not much syntactic difference between filteredListOfThings being a lambda parameter and it being an explicit variable; but you have more flexibility in what you can do whilst processing it (returning from the methods, throwing checked exceptions etc).
I'd like to stream the list, filter it, and then operate on the entire filtered result.
Note that the stream can be infinite as well ;)
So getting the infinite list of results is not a good idea.
Basically streams are lazy and applying an intermediate operations to stream without having a terminal operation does nothing:
For example the following code prints nothing:
Stream<String> stream = Stream.of("hello","how","are", "you").filter(this::startsWithH)
private boolean startsWithH(String elem) {
System.out.println("Filtering element " + elem);
return elem.startsWith("h");
}
Now, when you do apply a terminal operation, it will still work element-by-element usually:
Example of execution:
Stream<String> stream = Stream.of("hello","how","are", "you")
.filter(this::startsWithH)
.map(String::toUpperCase)
stream.collect(toList());
This example yields the following execution chain:
filter("hello")
map("hello")
filter("how")
map("how")
filter("are") <--- filtered out, no call to map will be done
filter("you") <--- filtered out, no call to map will be done
But if so, you can't really operate on the "whole" stream in the example provided in this question (ok, there are stateful operations that must work on the whole stream, like sort, but its an entirely different story).
In other words if you want to get the data as a collection you should, well, collect the data. It won't be a stream anymore.
For this, you should use .collect(). And if you do have an infinite stream, don't forget to call limit beforehands ;)

when to use map and forEach

I am learning Java 8 and came across a situation. Where in I have to iterate over a list of strings and then convert them to upperCase. The possible solutions would be to stream the list. Among many suggestions from Intellij the below two seems to be useful.
list.stream()
.map(String::toUpperCase)
or
list.stream().
forEach(p -> p.toUpperCase())
I am confused on which one to use and the use cases for all the Suggestions. Can I get help regarding which method to use and how to understand using all those suggestions?
Stream.map() will never run unless you end the pipeline in a terminal operation, like forEach(). But calling toUpperCase() in a forEach() won't do anything either, because strings are immutable. String.toUpperCase() doesn't change the string; it returns a new one.
If you just want to update the list in-place, you can use
list.replaceAll(String::toUpperCase);
which actually replaces each element with the result of the passed function.
If you want the results in a new list, use the map() snippet with a collector:
List<String> list2 = list.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
forEach is an terminal operation that makes a difference through side effects. map is a non-terminal operation that makes a direct mapping from one element to another. For example, here is a canonical usage of forEach:
stream.forEach(System.out::println);
This will invoke, on each element of the stream, the equivalent of System.out.println(element);. However, the stream will be closed after this, and no operations may be executed on stream afterwards. map, on the other hand, may be used like this:
streamOfObjects.map(Object::toString).collect(Collectors.toList());
In this case, each Object within streamOfObjects is mapped to a String, created by invocation of toString. Then, the stream of Strings produced by map is collected into a List using a Collector.
In any case, I'd suggest using replaceAll for this use case, as suggested by #shmosel.
As for how to understand suggestions provided by autocomplete, I would strongly suggest reading JavaDocs on the related classes.

Side effects of lambda expression in java 8

Please consider the below code snippet.
List<String> list = new ArrayList<String>();
list.add("A");
list.add("B");
list.add("C");
List<String> copyList = new ArrayList<String>();
Consumer<String> consumer = s->copyList.add(s);
list.stream().forEach(consumer);
Since we are using lambda expression, as per functional programming (pure functions) it should only compute the input & provide corresponding output.
But here in the example it is trying to add elements to the list which is neither input nor declared inside the lambda scope.
Is this a good practice, I mean, leading to any side effects?
forEach would be useless if it didn't produce side-effects, since it has no return value. Hence, whenever you use forEach you should be expecting side-effects to take place. Therefore there's nothing wrong with your example.
A Consumer<String> can print the String, or insert it into some database, or write it into some output file, or store it in some Collection (as in your example), etc...
From the Stream Javadoc:
A stream pipeline consists of a source (which might be an array, a collection, a generator function, an I/O channel, etc), zero or more intermediate operations (which transform a stream into another stream, such as Stream.filter(Predicate)), and a terminal operation (which produces a result or side-effect, such as Stream.count() or Stream.forEach(Consumer)).
Besides, if you look at the Javadoc of Consumer, you'll see that it's expected to have side-effects:
java.util.function.Consumer
Represents an operation that accepts a single input argument and returns no result. Unlike most other functional interfaces, Consumer is expected to operate via side-effects.
I guess this means Java Streams and functional interfaces were not designed to be used only for "purely" functional programming.
For a forEach or even a stream().forEach it is pretty straightforward. Your example works fine.
Be aware though that if you would do this with other streaming methods, then you could get some surprises: e.g. The following code prints absolutely nothing.
List<String> lst = Arrays.asList("a", "b", "c");
lst.stream().map(
s -> {
System.out.println(s);
return "-";
});
In this case, the stream acts more like a builder which prepares a process but does not execute it yet. It's only when a collect, count or find... method is called that the lambda is executed.
An easy way to spot this, is by looking at the return type of the map method, which in turn is again a Stream.
Having said that, I think for your specific example, there are easier alternatives.
List<String> list = Arrays.asList("A", "B", "C");
// this is the base pattern for transforming one list to another.
// the map applies a transformation.
List<String> copyList1 = list.stream().map(e -> e).collect(Collectors.toList());
// if it's a 1-to-1 mapping, then you don't really need the map.
List<String> copyList2 = list.stream().collect(Collectors.toList());
// in essence, you could of course just clone the list without streaming.
List<String> copyList3 = new ArrayList<>(list);

Process elements of Set<Foo> and create Set<Bar> using streams

I have a Set<String> of "hostname:port" pairs and from that I'd like to create a Set<InetSocketAddress>. I tried it like:
Set<InetSocketAddress> ISAAddresses = StrAddresses
.stream().map(addr -> new InetSocketAddress(
addr.split(":")[0],
Integer.parseInt(addr.split(":")[1])));
But this produces the following error in IntelliJ:
Incompatible types. Required Set<InetSocketAddress> but 'map' was
inferred to Stream<R>: no instance(s) of type variable(s) R exist so
that Stream<R> conforms to Set<InetSocketAddress>
Something must be wrong with how I'm using the map and the lambda.
The Stream#map function does not return a Map. It transforms (maps) the current elements of your stream to other elements. So it generates from a Stream<X> a Stream<Y> using the given transformation function which takes X and outputs Y.
StrAddresses.stream() // String
.map(addr -> new InetSocketAddress(
addr.split(":")[0],
Integer.parseInt(addr.split(":")[1]))); // InetSocketAddress
You start with a Stream<String> and end up with a Stream<InetSocketAddress>.
To quote from its documentation:
Returns a stream consisting of the results of applying the given function to the elements of this stream.
If you want to transform that stream into a Set you need to use the Stream#collect method like so:
StrAddresses.stream()
.map(addr -> new InetSocketAddress(
addr.split(":")[0],
Integer.parseInt(addr.split(":")[1])))
.collect(Collectors.toSet());
The utility method Collectors.toSet() returns a collector for a well optimized Set. If you for example explicitly want a HashSet you can use this instead:
.collect(Collectors.toCollection(HashSet::new));
From its documentation:
Performs a mutable reduction operation on the elements of this stream. A mutable reduction is one in which the reduced value is a mutable result container, such as an ArrayList [...]
As a small note, you currently split the same element twice each time:
addr.split(":")[0], // First
Integer.parseInt(addr.split(":")[1]))) // Second
You could save that additional split procedure by memorizing the value before. In this case this can be done elegantly by using a second Stream#map call. First we transform from Stream<String> to Stream<String[]> and then to Stream<InetSocketAddress>:
StrAddresses.stream() // String
.map(addr -> addr.split(":")) // String[]
.map(addrData -> new InetSocketAddress(
addrData[0], Integer.parseInt(addrData[1]))) // InetSocketAddress
.collect(Collectors.toSet());
Note that Stream#map is a lazy operation. This means that Java will not transform the whole Stream from A to B once you call the method. It will wait until a non-lazy (finalizing) operation like Stream#collect comes, then traverse the Stream and apply each lazy operation element-wise. So you can add as many Stream#map calls as you like without producing extra loops over the whole Stream.
You need to collect the Stream of InetSocketAddress addresses returned after mapping to Set. This can be done as -
Set<InetSocketAddress> ISAAddresses = StrAddresses.stream()
.map(addr -> new InetSocketAddress(addr.split(":")[0], Integer.parseInt(addr.split(":")[1])))
.collect(Collectors.toSet());

Combine stream of Collections into one Collection - Java 8

So I have a Stream<Collection<Long>> that I obtain by doing a series of transformations on another stream.
What I need to do is collect the Stream<Collection<Long>> into one Collection<Long>.
I could collect them all into a list like this:
<Stream<Collection<Long>> streamOfCollections = /* get the stream */;
List<Collection<Long>> listOfCollections = streamOfCollections.collect(Collectors.toList());
And then I could iterate through that list of collections to combine them into one.
However, I imagine there must be a simple way to combine the stream of collections into one Collection<Long> using a .map() or .collect(). I just can't think of how to do it. Any ideas?
This functionality can be achieved with a call to the flatMap method on the stream, which takes a Function that maps the Stream item to another Stream on which you can collect.
Here, the flatMap method converts the Stream<Collection<Long>> to a Stream<Long>, and collect collects them into a Collection<Long>.
Collection<Long> longs = streamOfCollections
.flatMap( coll -> coll.stream())
.collect(Collectors.toList());
You could do this by using collect and providing a supplier (the ArrayList::new part):
Collection<Long> longs = streamOfCollections.collect(
ArrayList::new,
ArrayList::addAll,
ArrayList::addAll
);
You don't need to specify classes when not needed.
A better solution is:
Collection<Long> longs = streamOfCollections.collect(
ArrayList::new,
Collection::addAll,
Collection::addAll
);
Say, you don't need an ArrayList but need a HashSet, then you also need to edit only one line.

Categories

Resources