How to call entrySet method on Mono<Map<Entity, Integer>> - java

Let's say that I have a method findAll which is returning Flux<Song> from ReactiveMongoRepository<Song, String>.
Below I have traditional java stream approach to find the x top-voted songs which is suggested by #Naman:
return songRepository.findAll()
.collect(Collectors.toMap(Function.identity(), this::getSumOfVotes))
.entrySet().stream() --entrySet method is not recognized by IntelliJ
.sorted(Map.Entry.<Song,Integer>comparingByValue().reversed())
.limit(numberOfTopSongs)
.collect(Collectors.toMap(e -> e.getKey().getId(), Map.Entry::getValue, (a, b) -> a, LinkedHashMap::new));
Above solution works for Iterable, Set and List but the problem occurs when I pretend to call method entrySet on Mono<Map<Song, Integer>>. That's my first dive deep into Web Flux and that's why I am a little bit confused why method entrySet is not available for Mono type.
I have found a piece of code like:
Mono<Map<ByteBuffer, ByteBuffer>> serializedMap = Flux.fromIterable(() -> map.entrySet().iterator())
.collectMap(entry -> rawKey(entry.getKey()), entry -> rawValue(entry.getValue()));
but I try to avoid repacking my:
songRepository.findAll()
.collect(Collectors.toMap(Function.identity(), this::getSumOfVotes))
to Flux.fromIterable again.
I will be grateful for suggestions on what I am doing wrong or tips on how to skip this obstacle.

It's Map<Song, Integer> which has entrySet method; if you want to apply some transformation to contents of a Mono, use map:
Mono<Map<Song, Integer>> songs = ...;
Mono<Set<Map.Entry<Song, Integer>> = songs.map(Map::entrySet);
Note that map is very common name for such a method, it's available on Flux, Stream, etc.

Related

Difference between Mono.then and Mono.flatMap/map

Say I want to call a webservice1 and then call webservice2 if the first was successful.
I can do the following (just indicative psuedo code) :-
Mono.just(reqObj)
.flatMap(r -> callServiceA())
.then(() -> callServiceB())
or
Mono.just(reqObj)
.flatMap(r -> callServiceA())
.flatMap(f -> callServiceB())
What is the difference between the two, when using the mono.just() for a single element?
flatMap should be used for non-blocking operations, or in short anything which returns back Mono, Flux.
map should be used when you want to do the transformation of an object /data in fixed time. The operations which are done synchronously.
For ex:
return Mono.just(Person("name", "age:12"))
.map { person ->
EnhancedPerson(person, "id-set", "savedInDb")
}.flatMap { person ->
reactiveMongoDb.save(person)
}
then should be used when you want to ignore element from previous Mono and want the stream to be finised
Here's a detailed explanation from #MuratOzkan
Copy pasting the TL DR answer:
If you care about the result of the previous computation, you can use map(), flatMap() or other map variant. Otherwise, if you just want the previous stream finished, use then().
In your example, looks like your service calls do not require the input of the upstream, then you could use this instead:
Mono.just(reqObj)
.then(() -> callServiceA())
.then(() -> callServiceB())

How to fetch the data from a collection of optionals?

I have a method returning a collection of products:
Collection<Product> getProducts() { ... }
Each product may have a guarantee. But it is not required.
interface Product {
Optional<Guarantee> getGuarantee();
}
Now I need to go through all the products and check if the quarantees have expired. The non-expired ones should be collected into a list.
This is what I do:
List<Optional<Guarantee>> optionalGar = getProducts().stream()
.map(f -> f.getGuarantee()).collect(Collectors.toList());
List<Guarantee> gar = optionalGar.stream()
.map(op -> op.orElse(null))
.filter(Objects::nonNull)
.filter(g -> !g.hasExpired())
.collect(Collectors.toList());
Is there any way to avoid using .orElse(null)?
(Replacing it by op.get() would cause an exception in case the optional is empty)
P.S: I'm free to chose between Java 8 and Java 9 so both solutions (not sure if the'll be different) are welcome
Java 8
List<Guarantee> expiredGuarantees = getProducts().stream()
.map(Product::getGuarantee)
.filter(Optional::isPresent)
.map(Optional::get)
.filter(not(Guarantee::hasExpired))
.collect(toList());
Java 9
Java9 has got Optional::stream. So you can replace filtering and mapping with single flatMap:
List<Guarantee> expiredGuarantees = getProducts().stream()
.map(Product::getGuarantee)
.flatMap(Optional::stream)
.filter(not(Guarantee::hasExpired))
.collect(toList());
Note
Java 8 does not have Predicates.not method. It's included since 11th version only.
By adding the following method to your project you'll be able to use it with the solutions above.
public static <T> Predicate<T> not(Predicate<T> predicate) {
return predicate.negate();
}
Update
Although this is not the CodeReview community, here are some notes on your code:
By combining the two pipelines into a single your code will be cleaner (in this particular case).
Prefer a method reference over a lambda when possible
Give appropriate names to your variables so you'll make your code easier to maintain

How to avoid nested forEach calls?

I have the following code:
interface Device {
// ...
boolean isDisconnected();
void reconnect();
}
interface Gateway {
// ...
List<Device> getDevices();
}
...
for (Gateway gateway : gateways) {
for(Device device : gateway.getDevices()){
if(device.isDisconnected()){
device.reconnect();
}
}
}
I want to refactor the code using Stream API. My first attempt was like the following:
gateways
.stream()
.forEach(
gateway -> {
gateway
.getDevices()
.parallelStream()
.filter(device -> device.isDisconnected())
.forEach(device -> device.reconnect())
;
}
)
;
I didn't like it so after some modifications I ended up with this code:
gateways
.parallelStream()
.map(gateway -> gateway.getDevices().parallelStream())
.map(stream -> stream.filter(device -> device.isDisconnected()))
.forEach(stream -> stream.forEach(device -> device.reconnect()))
;
My question is whether there is a way to avoid nested forEach.
You should flatten the stream of streams using flatMap instead of map:
gateways
.parallelStream()
.flatMap(gateway -> gateway.getDevices().parallelStream())
.filter(device -> device.isDisconnected())
.forEach(device -> device.reconnect());
I would improve it further by using method references instead of lambda expressions:
gateways
.parallelStream()
.map(Gateway::getDevices)
.flatMap(List::stream)
.filter(Device::isDisconnected)
.forEach(Device::reconnect);
Don't refactor your code into using Streams. You gain no benefits and gain no advantages over doing it like this, since the code is now less readable and less idiomatic for future maintainers.
By not using streams, you avoid nested forEach statements.
Remember: streams are meant to be side-effect free for safer parallelization. forEach by definition introduces side-effects. You lose the benefit of streams and lose readability at the same time, making this less desirable to do at all.
I would try this with a sequential stream before using a parallel one:
gateways
.stream()
.flatMap(gateway -> gateway.getDevices().stream())
.filter(device -> device.isDisconnected())
.forEach(device -> device.reconnect())
;
The idea is to create a stream via gateways.stream() then flatten the sequences returned from gateway.getDevices() via flatMap.
Then we apply a filter operation which acts like the if statement in your code and finally, a forEach terminal operation enabling us to invoke reconnect on each and every device passing the filter operation.
see Should I always use a parallel stream when possible?

Java 8 Stream Collectors for Maps, misleading groupingBy error

I'm pretty confused by the differences in these two methods, but I'm sure I'm doing something wrong.
I have a working example, and a non-working example below. In the working example, I'm assigning the variable tester as a "Map", In the non-working example I'm trying to assign it to Map. In the second example, the error is shown here:
I am failing to see the connection between the types for the tester variable, and the type of the myMap variable.
//Working Example (or at least it compiles)
public String execute(Map<String, String> hostProps) {
Map tester = usages.stream()
.map(usage -> prepareUsageForOutput(usage, rateCard.get()))
.collect(Collectors.groupingBy(myMap -> myMap.get("meterName"))); // This case compiles
}
//Compiler Error, just by adding the Map types on line 13, it breaks line 18
public String execute(Map<String, String> hostProps) {
Map<String, Object> tester = usages.stream()
.map(usage -> prepareUsageForOutput(usage, rateCard.get()))
.collect(Collectors.groupingBy(myMap -> myMap.get("meterName"))); // In this case, the .get() method call can't resolve .get. It doesn't recognize that myMap is a Map.
}
//This is the method that gets called in the above methods
static Map<String, Object> prepareUsageForOutput(Usage usage, RateCard rateCard ){
}
Update 1 - Wrong Collector Method
While Eran posted the explanation of my original issue, it revealed that I should have been using Collectors.toMap instead of Collectors.groupBy, because my goal was to return a map entry for each map returned from "prepare", where the key was a string, and the value was the map returned from "prepare". groupBy will always return a list of results as the map value.
I also removed the filter statements, as they were irrelevant and significantly complicated the example.
This was the final result that met my goal:
Map<String, Object> tester = usages.stream()
.map(usage -> prepareUsageForOutput(usage, rateCard.get()))
.collect(Collectors.toMap(myMap ->(String)myMap.get("meterName"), Function.identity()));
Since prepareUsageForOutput returns a Map<String, Object>, this means the Collectors.groupingBy() collector operates on a Stream<Map<String, Object>>, which means it creates a Map<Object,List<Map<String, Object>>>, not a Map<String, Object>.
This answer doesn't explain the error your compiler gave, but several times I encountered misleading compilation errors where Streams are involved, so I suggest changing the output type to Map<Object,List<Map<String, Object>>> and see if the error goes away.

Apply a stream of mappers to another stream in Java8

In Java8 I have a stream and I want to apply a stream of mappers.
For example:
Stream<String> strings = Stream.of("hello", "world");
Stream<Function<String, String>> mappers = Stream.of(t -> t+"?", t -> t+"!", t -> t+"?");
I want to write:
strings.map(mappers); // not working
But my current best way of solving my task is:
for (Function<String, String> mapper : mappers.collect(Collectors.toList()))
strings = strings.map(mapper);
strings.forEach(System.out::println);
How can I solve this problem
without collecting the mappers into a list
without using a for loop
without breaking my fluent code
Since map requires a function that can be applied to each element, but your Stream<Function<…>> can only be evaluated a single time, it is unavoidable to process the stream to something reusable. If it shouldn’t be a Collection, just reduce it to a single Function:
strings.map(mappers.reduce(Function::andThen).orElse(Function.identity()))
Complete example:
Stream<Function<String, String>> mappers = Stream.of(t -> t+"?", t -> t+"!", t -> t+"?");
Stream.of("hello", "world")
.map(mappers.reduce(Function::andThen).orElse(Function.identity()))
.forEach(System.out::println);

Categories