There are few question , but there answers are very specific to some code.
Generally, how to convert a Stream of Mono to Flux
List<Mono<String> listOfMono = stream()
.map( s -> { do something and return Mono<String> } )
.collect(Collectors.toList());
How to convert listOfMono object to Flux<String>
You can use fromIterable and then use flatMap to flatten the Mono
Transform the elements emitted by this Flux asynchronously into Publishers, then flatten these inner publishers into a single Flux through merging, which allow them to interleave.
Flux<String> result = Flux.fromIterable(listOfMono)
.flatMap(Function.identity());
If your input is a list of Monos you can simply do:
Flux.merge(listOfMono);
If your input is stream you can either do
stream()
.map( s -> { do something and return Mono<String> } )
.collect(Collectors.collectingAndThen(Collectors.toList(), Flux::merge));
OR
Flux.fromStream(stream())
.flatMap( s -> { do something and return Mono<String> } )
I'd personally prefer the last option as that is the most straighforward and idiomatic.
Related
Given an infinite flux of objects, where each object has an ID, how can I use flux to create a buffered list of updates grouped by ID property (keeping the last emitted value)?
Thanks
Example
Obj(ID=A, V=1)
Obj(ID=A, V=2)
Obj(ID=B, V=3)
--- buffer -> I want to subscribe with a list of [Obj(ID=A, V=2), Obj(ID=B, V=3)]
Obj(ID=A, V=1)
Obj(ID=B, V=4)
Obj(ID=B, V=6)
Obj(ID=A, V=2)
--- buffer -> I want to subscribe with a list of [Obj(ID=B, V=6), Obj(ID=A, V=2)]
Obj(ID=B, V=1)
--- buffer -> I want to subscribe with a list of [Obj(ID=B, V=1)]
Something like the following would be perfect but it seems to wait the end of the flux in my tests instead of buffering.
flux
.buffer(Duration.ofMillis(2000))
.groupBy(Obj::getId)
.flatMap(GroupedFlux::getLast)
.collectToList()
.subscribe(this::printList);
It works with buffer and custom logic for grouping
public static void main(String[] args) {
flux.buffer(Duration.ofMillis(2000)).subscribe(this::groupList);
}
private void groupList(List<T> ts) {
Collection<T> values = ts.stream()
.collect(Collectors.toMap(T::getId, Function.identity(), (k, v) -> v))
.values();
System.out.println(values);
}
buffer will emit List<T>, therefore you could just use non-reactive java to group by. For example, java streams like in your example. Assuming your process function is reactive, you could continue the flow
flux
.buffer(Duration.ofMillis(2000))
.map(list ->
list.stream()
.collect(Collectors.toMap(Entry::getId, Function.identity(), (k, v) -> v)))
.flatMapIterable(Map::values)
.flatMap(obj -> process(obj));
I was able to achieve it with the reactive grouping
flux.window(Duration.ofMillis(2000))
.flatMap(window -> window.groupBy(Entry::getId)
.flatMap(GroupedFlux::last)
.collectList()
)
.subscribe(this::printList);
I try to get my head around java streams.
Atm I have this construct which does not work:
List<int[]> whiteLists = processes.stream()
.map(Process::getEventList)
.forEach(eventList -> eventList.stream()
.map(event -> event.getPropertie("whitelist"))
.map(propertie -> propertie.getIntArray())
.collect(Collectors.toList()));
}
The hierarchy is:
Process
Event
Property
Process::getEventList returns a list of Event objects
event.getPropertie("whitelist") returns a Property objects which hast the method getIntArray()
event.getPropertie() gives me an int-array.
How do I collect these array into a List of arrays?
Thanks!
You can't use forEach() as it takes a Consumer, meaning it will consume the stream, but can't return anything (so nothing to collect).
You need flatMap to stream the internal eventList as follows
List<int[]> whiteLists = processes.stream()
.flatMap(p -> p.getEventList().stream())
.map(event -> event.getPropertie("whitelist"))
.map(propertie -> propertie.getIntArray())
.collect(Collectors.toList());
Is there a way to use the stream result (List<Integer> in my case) to perform an operation inside the function on that list (all at once)
So instead of this:
var cardTypesToRemove = existingIds.stream()
.filter(c -> !cardTypeIds.contains(c))
.collect(Collectors.toList());
repository.deleteBy(cardTypesToRemove);
Something like this (excuse me for pseudo code)
var cardTypesToRemove = existingIds.stream()
.filter(c -> !cardTypeIds.contains(c))
.collect(Collectors.collectingAndThen(repository.saveAll(resultList)));
No. Just pass the result of the collect as a regular method parameter.
var cardTypesToRemove = repository.saveAll(
existingIds.stream()
.filter(c -> !cardTypeIds.contains(c))
.collect(Collectors.toList());
I want to work with CompletableFuture and Stream at the same time. Something like transforming a CompletableFuture<Stream<String>> into a CompletableFuture<Stream<Team>>.
I have done something similar from CompletableFuture<String> to CompletableFuture<Integer>.
CompletableFuture<Stream<String>> getMostPopularTeamNames(int maxResults) {
CompletableFuture<Stream<String>> mostPopularTeamNames;
WorldCupSocialApi.getMostPopularTeamNames(1, maxResults, teams -> {
mostPopularTeamNames = CompletableFuture.supplyAsync(() -> teams.stream());
});
return mostPopularTeamNames;
}
I am trying to do something like:
CompletableFuture<Stream<Team>> cf = getMostPopularTeamNames(5).thenApply(s -> {
s.map(name -> new Team(name))
}
But I am not sure if I the s is going to be the String or the Stream, and if I can map it that way...
So, let's dissect the problem into two smaller problems:
Converting a CompletableFuture<T> to CompletableFuture<R>
Converting a Stream<String> into Stream<Team>
The first one can be achieved using thenApply or thenApplyAsync.
The second one can be achieved using a standard map operation on a Stream instance.
And now once you try to combine them, think about just like about applying any other transformation of CompletableFuture<String>, and now it should become clear that you can simply do:
CompletableFuture<Stream<Team>> cf = getMostPopularTeamNames(5)
.thenApply(s -> s.map(name -> new Team(name));
s is a Stream<String> that gets converted into Stream<Team> - there's no magical conversion between these two types
I'm in a bit of confusion right now, so I have a method that should return CompletableFuture<List<A>>
inside the method is:
CompletableFuture<List<String>> toReturn = asyncCall().thenApply(....)
.thenCompose(listOfStuff -> convertToList(listOfStuff.stream().map(
key -> asyncCall2(key)
.thenApply(optionalValue -> optionalValue.orElse(null))
).collect(Collectors.toList()));
and convertToList() simply joins futures to convert CompletableFuture<List<ComputableFuture<A>>> into CompletableFuture<List<A>>
Basically my intention is to filter null values that emerge from optionalValue.orElse(null) And it would be easy to do filter before collecting it all to list in the last line, but if I use it just before .collect it is working over CompletableFutures
I suspect there's a lot of restructuring I can do in my code.
EDIT:
private<T> CompletableFuture<List<T>> convertToList(List<CompletableFuture<T>> toConvert) {
return CompletableFuture.allOf(toConvert.toArray(new CompletableFuture[toConvert.size()]))
.thenApply(v -> toConvert.stream()
.map(CompletableFuture::join)
.collect(Collectors.toList())
);
}
The best way would probably be to change convertToList() so that it does not return a future of list, but of stream instead:
private <T> CompletableFuture<Stream<T>> convertToFutureOfStream(List<CompletableFuture<T>> toConvert) {
return CompletableFuture.allOf(toConvert.stream().toArray(CompletableFuture[]::new))
.thenApply(
v -> toConvert.stream()
.map(CompletableFuture::join)
);
}
This will be more reusable as the method will allow better chaining and will not force the caller to work with a list, while still allowing to easily get a list with a simple collect.
You can then simply filter that stream to remove empty optionals:
CompletableFuture<List<String>> toReturn = asyncCall()
.thenCompose(listOfStuff -> convertToFutureOfStream(
listOfStuff.stream()
.map(this::asyncCall2)
.collect(Collectors.toList())
)
.thenApply(stream ->
stream.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList())
)
);
You can even improve this a little further by changing convertToFutureOfStream() to take a stream as argument as well:
private <T> CompletableFuture<Stream<T>> convertToFutureOfStream(Stream<CompletableFuture<T>> stream) {
CompletableFuture<T>[] futures = stream.toArray(CompletableFuture[]::new);
return CompletableFuture.allOf(futures)
.thenApply(v -> Arrays.stream(futures).map(CompletableFuture::join));
}
(unfortunately this raises an unchecked assignment warning because of the array of generic types)
Which then gives
CompletableFuture<List<String>> toReturn = asyncCall()
.thenCompose(listOfStuff -> convertToFutureOfStream(
listOfStuff.stream().map(this::asyncCall2)
)
.thenApply(stream ->
stream.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList())
)
);