Correct use of Java 8 supplier consumer - java

I'm still strugling with Suppliers and Consumers for Java 8, I have this:
final Set<String> roles = new HashSet<>();
user.getRoleGroups().forEach(rg -> rg.getRoles().forEach(r -> roles.add(r.getName())));
To get a Set from role names that are inside a list of Roles inside a list of RoleGroups.
Pretty sure I could use something in one line with .stream().map() and RoleGroup::getRoles and Role::getName to get this Set. But I don't know how.

You are pretty close! To use a Stream instead, do something like this:
final Set<String> roles = user.getRoleGroups().stream()
.flatMap(g -> g.getRoles().stream())
.map(Role::getName)
.collect(Collectors.toSet());
Use of flatMap() is the only tricky part here. The flatMap() operation transforms an element to a Stream, which is concatenated with the Streams from the other elements.

Related

Terminal operation to evaluate intermediate operation

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)

Return List from forEach using Stream in java

I am trying to convert below for-loop to forEach method with help Stream function using Java1.8, but I was messed up and also confused to do that.
List<A> valueList = new ArrayList<>();
List<B> responseList = getResponses();
List<A> value = new ArrayList<>();
for (B getResponse: responseList) {
valueList = getValues(getResponse);
value.addAll(valueList);
}
With streams you generally want to avoid creating empty lists and then adding items. Streams should use functional idioms and avoid side effects as much as possible. It's better to work with the stream as a whole and then "collect" the results into a list at the end.
List<C> value = getResponses().stream()
.flatMap(r -> getValues(r).stream())
.collect(Collectors.toList());
I am trying to convert below for-loop to forEach method with help
Stream function using Java 1.8.
You shouldn't use a stream along with forEach simply to accumulate into a predefined list as there will be side effects (which should be avoided when dealing with streams), rather go with the stream approach suggested by John Kugelman if you want to perform it with streams or using the forEach method it can also be done as:
List<A> value = new ArrayList<>();
responseList.forEach(response -> value.addAll(getValues(response))));
It appears you are trying to add all the values in responseList to valueList and I think you could just do valueList.addAll(responseList); and not have to use a for loop at all.
You could have a problem though if type B doesn't inherit from A because you can't have a list of two unrelated types.

Collect all values of a Set field

I have a collection which has a field of type Set with some values. I need to create a new set collecting all these values.
I am wondering if this is possible using lambda expressions.
Below is the code line :
Set<String> teacherId = batches.stream()
.filter(b -> !CollectionUtils.isEmpty(b.getTeacherIds()))
.map(b -> b.getTeacherIds())
.collect(Collectors.toSet());
The problem is post map operation, it contains a collection of set of strings. So collect operation returns a Set<Set<String>> but i am looking to aggregate all the values to a single set.
You need to use flatMap instead of map:
Set<String> teacherIds =
batches.stream()
.flatMap(b -> b.getTeacherIds().stream())
.collect(Collectors.toSet());
Note that the filtering is redundant for empty collections - streaming an empty collection will just result in an empty stream, which won't affect the final result.
If getTeacherIds() could return null, however, you'd still have to handle it. Using filter(Objects::nonNull) would suffice, and save you the dependency on Apache Commons.
You can use flatMap to obtain a flat Stream of all the values, and then collect to a Set<String>:
Set<String> teacherId =
batches.stream()
.filter(b -> !CollectionUtils.isEmpty(b.getTeacherIds()))
.flatMap(b -> b.getTeacherIds().stream())
.collect(Collectors.toSet());
If you care that that getTeacherIds() is not null, use it explicitly via !=, that CollectionUtils.isEmpty just hides stuff. Especially since if getTeacherIds() returns an Empty collection - that is handled just fine by flatMap, so to me that is not needed at all.
Set<String> teacherIds = batches
.stream()
.filter(x -> x.getTeacherIds() != null)
.flatMap(x -> x.getTeacherIds().stream())
.collect(Collectors.toSet());
I am wondering if this is possible using lambda expressions.
I capture the last fish, :).
Set<String> teacherIds = batches.stream()//v--- the class of `x`
.map(XClass::getTeacherIds)
.filter(Objects::nonNull)
.flatMap(Collection::stream)
.collect(Collectors.toSet());
Note: I'm sorry I'm forget to tell you if the getTeacherIds copy the internal IDs to a new set of IDs, the code above is appropriate for you. since it is read the IDs from XClass once.

Java 8 Streams - Map Multiple Object of same type to a list using streams

Is it possible to do the below mentioned steps using streams in a better way ?
Set<Long> memberIds = new HashSet<>();
marksDistribution.parallelStream().forEach(marksDistribution -> {
memberIds.add(marksDistribution.getStudentId());
memberIds.add(marksDistribution.getTeacherId());
});
instanceDistribution.getStudentId() and instanceDistribution.getTeacherId() are both of type Long.
It might be possible that this kind of question is asked but I am not able to understand it. In simple yes or no. If yes/no, then how and bit explanation. And if possible kindly, discuss the efficiency.
Yes, you can use flatMap to map a single element of your Stream into a Stream of multiple elements, and then flatten them into a single Stream :
Set<Long> memberIds =
marksDistribution.stream()
.flatMap (marksDistribution -> Stream.of(marksDistribution.getStudentId(), marksDistribution.getTeacherId()))
.collect(Collectors.toSet());
You can use the 3-args version of collect:
Set<Long> memberIds =
marksDistribution.parallelStream()
.collect(HashSet::new,
(s, m) -> {
s.add(m.getStudentId());
s.add(m.getTeacherId());
}, Set::addAll);
Your current version may produce wrong results, since you are adding elements in parallel in a non-thread safe collection. So it may be possible that you have multiple times the same value in the set.

List containing another list to set using lambda in java

I have a list containing Persons, each person has a list with his subjects inside.
I need to return a Set containing every subject using lambda, so far i've tried this:
list.stream().map(person -> person.getSubjects());
But that would get me a List> so i can't use it.
How could i print/get every string in the list of every person using lambdas?
Thanks.
list.stream().map(person -> person.getSubjects().stream()); is not a List, it's a Stream. If you want a Set, do this :
list.stream().flatMap(person -> person.getSubjects().stream()).collect(Collectors.toSet());
This will create an HashSet<Subject>. Note the use of flatMap instead of map to flatten the lists of subjects into a single stream. If you want another implementation of Set, for example TreeSet, do the following :
list.stream().flatMap(person -> person.getSubjects().stream())
.collect(Collectors.toCollection(TreeSet::new));
You can use flatMap:
Set<Subject> subjects = list.stream()
.map(person -> person.getSubjects())
.flatMap(subjects -> subjects.stream())
.collect(Collectors.toSet());
flatMap is good for "flattening" nested collections.

Categories

Resources