How to use completablefuture and Streams together - java

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

Related

Java 8 Streams - Call "map" during stream operations without saving to variable

I could not find many answers to this, though I suppose its not possible, but I was curious - is there anyway in Java8 streams to "refer" or to "call" the HashMap that is created via the .collectors(Collectors.toMap(key -> "key", val -> "val")? I know I can save this resultant reduction which creates the Map to Map<String, String> myMap variable, but I still had to perform operations on this resultant HashMap and I wanted to keep the stream going, so I wanted to do something like this:
myHashSet.stream().
.filter(i -> i != null)
.collectors(Collectors.toMap(key -> "key", val -> "val")
.forEach((k,v) -> {
// HERE IS WHERE I WANT TO CALL THE MAP CREATED ABOVE
if( MAP.contains("someRandomValue") {
}
}));
I'm assuming you can't do this, but I was hoping there was some method or something so I don't have to "kill" the stream, i.e. save the Map to a variable, then proceed to stream it out again etc...
The collect operation in Stream is a terminal operation. So there is no Stream after collect. You would have to do something like below.
var myMap = myHashSet.stream().
.filter(i -> i != null)
.collectors(Collectors.toMap(key -> "key", val -> "val");
myMap.forEach((k,v) -> {
// HERE IS WHERE I WANT TO CALL THE MAP CREATED ABOVE
if( MAP.contains("someRandomValue") {
}
}));
Below article describes about the available intermediate operations in Stream.
https://www.javacodegeeks.com/2020/04/java-8-stream-intermediate-operations-methods-examples.html
I solved this with #shmosel's help using collectingAndThen. This was the closest to what I wanted to do.
myArrayList.stream().
.collect(Collectors.collectingAndThen(Collectors.toMap(key -> key.getName(), val -> val), map -> map.forEach((key, val) -> System.out.println(map.contains(key)));

Stream: Filter on children, return the parent

Assume a class MyClass:
public class MyClass {
private final Integer myId;
private final String myCSVListOfThings;
public MyClass(Integer myId, String myCSVListOfThings) {
this.myId = myId;
this.myCSVListOfThings = myCSVListOfThings;
}
// Getters, Setters, etc
}
And this Stream:
final Stream<MyClass> streamOfObjects = Stream.of(
new MyClass(1, "thing1;thing2;thing3"),
new MyClass(2, "thing2;thing3;thing4"),
new MyClass(3, "thingX;thingY;thingZ"));
I want to return every instance of MyClass that contains an entry "thing2" in myCSVListOfThings.
If I wanted a List<String> containing myCSVListOfThings this could be done easily:
List<String> filteredThings = streamOfObjects
.flatMap(o -> Arrays.stream(o.getMyCSVListOfThings().split(";")))
.filter("thing2"::equals)
.collect(Collectors.toList());
But what I really need is a List<MyClass>.
This is what I have right now:
List<MyClass> filteredClasses = streamOfObjects.filter(o -> {
Stream<String> things = Arrays.stream(o.getMyCSVListOfThings().split(";"));
return things.anyMatch(s -> s.equals("thing2"));
}).collect(Collectors.toList());
But somehow it does not feel right. Any cleaner solution than opening a new Stream inside of a Predicate?
Firstly, I recommend you to add extra method to MyClass public boolean containsThing(String str), so you can transform you code like this:
List<MyClass> filteredClasses = streamOfObjects
.filter(o -> o.containsThing("thing2"))
.collect(Collectors.toList());
Now you can implement this method as you want depends on input data: splitting into Stream, splitting into Set, even searching of substring (if it's possible and has sense), caching result if you need.
You know much more about usage of this class so you can make right choice.
One solution is to use a pattern matching that avoids the split-and-stream operation:
Pattern p=Pattern.compile("(^|;)thing2($|;)");
List<MyClass> filteredClasses = streamOfObjects
.filter(o -> p.matcher(o.getMyCSVListOfThings()).find())
.collect(Collectors.toList());
Since the argument to String.split is defined as regex pattern, the pattern above has the same semantic as looking for a match within the result of split; you are looking for the word thing2 between two boundaries, the first is either, the beginning of the line or a semicolon, the second is either, the end of the line or a semicolon.
Besides that, there is nothing wrong with using another Stream operation within a predicate. But there are some ways to improve it. The lambda expression gets more concise if you omit the obsolete local variable holding the Stream. Generally, you should avoid holding Stream instances in local variables as chaining the operations directly will reduce the risk of trying to use a Stream more than one time. Second, you can use the Pattern class to stream over the resulting elements of a split operation without collecting them all into an array first:
Pattern p=Pattern.compile(";");
List<MyClass> filteredClasses = streamOfObjects
.filter(o -> p.splitAsStream(o.getMyCSVListOfThings()).anyMatch("thing2"::equals))
.collect(Collectors.toList());
or
Pattern p=Pattern.compile(";");
List<MyClass> filteredClasses = streamOfObjects
.filter(o -> p.splitAsStream(o.getMyCSVListOfThings()).anyMatch(s->s.equals("thing2")))
.collect(Collectors.toList());
Note that you could also rewrite your original code to
List<MyClass> filteredClasses = listOfObjects.stream()
.filter(o -> Arrays.asList(o.getMyCSVListOfThings().split(";")).contains("thing2"))
.collect(Collectors.toList());
Now, the operation within the predicate is not a Stream but a Collection operation, but this doesn’t change the semantic nor the correctness of the code…
As I see it you have three options.
1) look for particular entry in the String without spliting it - still looks messy
List<MyClass> filteredClasses = streamOfObjects
.filter(o -> o.getMyCSVListOfThings().contains(";thing2;"))
.collect(Collectors.toList());
2) map twice - still messy
List<MyClass> filteredClasses = streamOfObjects
.map(o -> Pair<MyClass, List<String>>.of(o, toList(o.getMyCSVListOfThings()))
.filter(pair -> pair.getRight().contains("thing2"))
.map(pair -> pair.getLeft())
.collect(Collectors.toList());
where toList is a method that will convert String to List
3) create additional field - method I'd suggest
Extend class MyClass - add field to the class
List<String> values;
And initialize it in the constructor:
public MyClass(Integer myId, String myCSVListOfThings) {
this.myId = myId;
this.myCSVListOfThings = myCSVListOfThings;
this.values = toList(myCSVListOfThings);
}
And then in the stream simply:
List<MyClass> filteredClasses = streamOfObjects
.filter(o -> o.getValues().contains("thing2"))
.collect(Collectors.toList());
Of course field values can be initialized in LAZY mode during first getValues method call if you want.
This is similar to the issue, Getting only required objects from a list using Java 8 Streams, posted a year earlier. I think the solution I left there is applicable here.
There's a library called com.coopstools.cachemonads. It extends the java stream (and Optional) classes to allow caching of entities for later use.
The solution can be found with:
List<Parent> goodParents = CacheStream.of(parents)
.cache()
.map(Parent::getChildren)
.flatMap(Collection::stream)
.map(Child::getAttrib1)
.filter(att -> att > 10)
.load()
.distinct()
.collect(Collectors.toList());
where, parents is an array or stream.
For clarity, the cache method is what stores the parents; and the load method is what pulls the parents back out. And If a parent does not have children, a filter will be needed after the first map to remove the null lists.
More specifically, for your issue:
List<Parent> goodParents = CacheStream.of(streamOfObjects)
.cache()
.map(o -> o.getMyCSVListOfThings().split(";"))
.flatMap(Collection::stream)
.filter("thing2"::equals)
.load()
.collect(Collectors.toList())
This library can be used in any situation where operations need to be performed on children, including map/sort/filter/etc, but where an older entity is still needed. There may be more lines than some of the other answers, but each line is very clean and straight forward.
Please let me know if this answer is helpful.
The code can be found at https://github.com/coopstools/cachemonads or can be downloaded from maven:
<dependency>
<groupId>com.coopstools</groupId>
<artifactId>cachemonads</artifactId>
<version>0.2.0</version>
</dependency>
(or, gradle, com.coopstools:cachemonads:0.2.0)

How to propagate variables in a stream in java 8

I would be currious to know how to propagate variable into a stream in java 8.
An example is better than a long explaination, so how would you convert the following (abstract) code into streams:
Map<Integer,A> myMap = new HashMap();
for (Entry<Integer,A> entry : myMap)
{
int param1=entry.getValue().getParam1();
List param2=entry.getValue().getParam2();
for (B b : param2)
{
System.out.println(""+entry.getKey()+"-"+param1+"-"+b.toString());
}
}
Knowing that this example is a simplification of the problem (for example, i need "param1" more than once in the next for loop)
So far, the only idea i have is to store all the informations i need into a tuple to finally use the forEach stream method over this tuple.
(Not sure to be very clear....)
Edit:I simplified my example too much. My case is more something like that:
Map<Integer,A> myMap = new HashMap();
for (Entry<Integer,A> entry : myMap)
{
int param1=entry.getValue().getParam1();
CustomList param2=entry.getValue().getParam2();
for (int i = 0; i<param2.size(); i++)
{
System.out.println(""+entry.getKey()+"-"+param1+"-"+param2.get(i).toString());
}
}
I could write something like that with stream:
myMap.entrySet().stream()
.forEach(
e -> IntStream.range(0, e.getValue.getParam2().getSize())
.forEach(
i -> System.out.println(e.getKey()+"-"+e.getValue().getParam1()+"-"+e.getValue.getParam2.get(i))
)
);
However, what i have instead of "e.getValue.getParam2()" in my real case is much more complex (a sequence of 5-6 methods) and heavier than just retrieving a variable (it executes some logic), so i would like to avoid to repeat e.getValue.getParam2 (once in just before the forEach, and once in the forEach)
i know that it's maybe not the best use case for using stream, but I am learning about it and would like to know about the limits
Thanks!
Something like this:
myMap.forEach(
(key, value) -> value.getParam2().forEach(
b -> System.out.println(key+"-"+value.getParam1()+"-"+b)
)
);
That is, for each key/value pair, iterate through value.getParam2(). For each one of those, print out string formatted as you specified. I'm not sure what that gets you, other than being basically what you had before, but using streams.
Update
Responding to updates to your question, this:
myMap.forEach((key, value) -> {
final CustomList param2 = value.getParam2();
IntStream.range(0, param2.getSize()).forEach(
i -> System.out.println(key+"-"+value.getParam1()+"-"+param2.get(i))
)
});
Here we assign the result of getParam2() to a final variable, so it is only calculated once. Final (and effectively final) variables are visible inside lambda functions.
(Thank you to Holger for the suggestions.)
Note that there are more features in the Java 8 API than just streams. Especially, if you just want to process all elements of a collection, you don’t need streams.
You can simplify every form of coll.stream().forEach(consumer) to coll.forEach(consumer). This applies to map.entrySet() as well, however, if you want to process all mappings of a Map, you can use forEach on the Map directly, providing a BiConsumer<KeyType,ValueType> rather than a Consumer<Map.Entry<KeyType,ValueType>>, which can greatly improve the readability:
myMap.forEach((key, value) -> {
int param1 = value.getParam1();
CustomList param2 = value.getParam2();
IntStream.range(0, param2.size()).mapToObj(param2::get)
.forEach(obj -> System.out.println(key+"-"+param1+"-"+obj));
});
It’s worth thinking about adding a forEach(Consumer<ElementType>) method to your CustomList, even if the CustomList doesn’t support the other standard collection operations…

anyMatch inside allMatch

I have a couple of predicates that I all want to be satisfied.
The things that can satisfy those predicates are a handful of strings. An individual string doesn't have to satisfy all (or any) of those predicates, but after I've looked at the last string, all of the predicates have to be satisified.
My first take to represent this problem in Java was to use Stream's allMatch and anyMatch since I want all of the predicates to match any of the things to test:
Stream<String> thingsToTest = Stream.of("Hi", "predicates!", "oddball");
Predicate<String> startsWithH = string -> string.startsWith("H");
Predicate<String> endsWithBang = string -> string.endsWith("!");
Stream<Predicate<String>> predicates = Stream.of(startsWithH, endsWithBang);
// All of the strings have the chance to satisfy any predicate
boolean predicatesSatisfied = predicates.allMatch(pred -> thingsToTest.anyMatch(pred::test));
// I expect this to print "true"
System.out.println(predicatesSatisfied);
Sadly, this doesn't work but terminates with an IllegalStateException, telling me that the stream has already been operated upon or closed, which shouldn't come as a big surprise since for each predicate I give the strings a new chance to satisfy the predicate, using the string stream over and over.
And streams are not meant to be reused for good reasons.
So how do I avoid this exception? Is there a more elegant alternative to anyMatch or allMatch?
To get around the IllegalStateException I use a List of strings and call its stream() method:
// Use List instead of Stream
List<String> thingsToTest = Arrays.asList("Hi", "predicates!", "oddball");
// Same old
Predicate<String> startsWithH = string -> string.startsWith("H");
Predicate<String> endsWithBang = string -> string.endsWith("!");
Stream<Predicate<String>> predicates = Stream.of(startsWithH, endsWithBang);
// Call stream() on the List
boolean predicatesSatisfied = predicates.allMatch(pred -> thingsToTest.stream().
anyMatch(pred::test));
Although this works fine, I'm not sure if it is the most elegant way to do this, so if you have a better idea, please go ahead and post your code or suggestion.
When you need to use the Stream several times, the common solution is to create a Supplier<Stream> instead:
Supplier<Stream<String>> thingsToTest = () -> Stream.of("Hi", "predicates!", "oddball");
....
boolean predicatesSatisfied = predicates.allMatch(
pred -> thingsToTest.get().anyMatch(pred::test));
Unlike #MatthiasBraun suggestion, using the Supplier it's not always necessary to actually store all the stream elements in the verbatim collection. For example, such thing is possible:
Supplier<Stream<String>> thingsToTest =
() -> IntStream.range(0, 10000).mapToObj(String::valueOf);
You just have to care that supplier always returns the same stream elements.
If you already have a collection, then you can create a supplier as well:
List<String> list = Arrays.asList("Hi", "predicates!", "oddball");
Supplier<Stream<String>> thingsToTest = list::stream;

convert list of completable futeres to one completable future of list

I have a list of CompletableFuture instances.
List<CompletableFuture<String>> listOfFutures;
How is it to convert them to one future like this:
CompletableFuture<List<String>> futureOfList = convert(listOfFutures);
This is a monadic sequence operation. With the cyclops-monad-api (a library I wrote) you can write
AnyM<Stream<String>> futureStream = AnyMonads.sequence(
AsAnyMList.completableFutureToAnyMList(futures));
CompletableFuture<Stream<String>> futureOfList = futureStream.unwrap();
When you call a terminal operation on the Stream inside futureOfList, e.g. to convert to a List, it will trigger the join() call on all the original futures, so should be used in a similar manner to join() itself.
CompletableFuture<List<String>> completed = futureOfList.thenApply(
s->s.collect(Collectors.toList());
To write your own version specifically for CompletableFuture you could do something like this
CompletableFuture<Stream<String>> futureOfList = CompletableFuture.completedFuture(1)
.thenCompose(one->listOfFutures.stream()
.map(cf->cf.join()));
Then to join
CompletableFuture<List<String>> completed = futureOfList.thenApply(
s->s.collect(Collectors.toList());
See also this question and answer for a solution using allOf (which won't block any additional threads).
You can do it like this:
public static <T> CompletableFuture<List<T>> convert(List<CompletableFuture<T>> futures) {
return futures.stream().
map(f -> f.thenApply(Stream::of)).
reduce((a, b) -> a.thenCompose(xs -> b.thenApply(ys -> concat(xs, ys)))).
map(f -> f.thenApply(s -> s.collect(toList()))).
orElse(completedFuture(emptyList()));
}

Categories