I would like to know if there is any difference in the behavior between those both methods or if it's just a matter of style:
private Single<JsonObject> foo() {
return Single.just(new JsonObject()).flatMap(next -> Single.just(next));
}
private Single<JsonObject> bar() {
return Single.just(new JsonObject()).map(next -> next);
}
There is no difference in behavior as both are pointless operations. The first simply repeats wrapping the object into a Single, while the second maps it to itself. You would never have a reason to do either.
Read up on 'flatMap()' and 'map()': the first turns each value into an observable of different values, the second turns each value into a different value.
You can represent for your self a flatMap operator like a sequence of two other operator map and merge.
Map will convert your source item to Observable that emit a value based on the function inside of map.
At this point merge will help to put together every item that emitted by each of your new observables, not the source one.
There is a good illustration on that book https://www.manning.com/books/rxjava-for-android-developers
map with merge together
To simplify this code was introduced flatMap operator
only flatMap
Related
I have two methods: funca() and funcb() which return a value of type X or a List<X> respectively like shown below:
X funca(Event e) { ... }
List<X> funcb(Event e) { ... }
I want to use them in the Stream and collect the result into a list.
These method methods should be called under different conditions, like shown below in pseudocode:
List<Event> input = // initializing the input
List<X> resultList = input.values().stream()
.(event -> event.status=="active" ? funca(event) : funcb(event))
.collect(Collectors.toList());
Can someone please tell me how I can achieve this so that whether the function returns a list of values or values?
Since one of your functions produces a Collection as a result, you need a stream operation that allows performing one-to-many transformation. For now, Stream IPA offers two possibilities: flatMap() and mapMulti().
To approach the problem, you need to take a closer look at them and think in terms of these operations.
flatMap()
This operation requires a function producing a Stream, and elements of this new stream become a replacement for the initial element.
Therefore, you need to wrap the result returned by the funca() with Singleton-Stream using Stream.of() (there's no need for wrapping the element with a List, like shown in another answer flatMap() is not capable to consume Collections).
List<X> = input.values().stream()
.flatMap(event -> "active".equals(event.getStatus()) ?
Stream.of(funca(event)) : funcb(event).stream()
)
.toList(); // for Java 16+ or collect(Collectors.toList())
mapMulti()
This operation was introduced with Java 16 and is similar to flatMap() but acts differently.
Contrary to flatMap it doesn't consume a new Stream. As an argument it expects a BiConsumer. Which in turn takes a stream element and a Consumer of the resulting type. Every element offered to the Consumer becomes a part of the resulting stream.
mapMulti() might be handy if funcb() produces a list which is very moderate in size (refer to documentation linked above for more details), otherwise flatMap() would be the right tool.
List<X> = input.values().stream()
.<X>mapMulti((event, consumer) -> {
if ("active".equals(event.getStatus())) consumer.accept(funca(event));
else funcb(event).forEach(consumer);
})
.toList(); // for Java 16+ or collect(Collectors.toList())
Sidenote: don't use == to compare reference types (like String) unless you need to make sure that both references are pointing to the same object, use equals() method instead.
Embed the result of funcA into a list and flatMap the lists:
List<X> result = input.stream()
.flatMap(e -> e.status.equals("active") ? List.of(funcA(e)) : funcB(e))
.collect(Collectors.toList());
I have the following data set with Key is String and value as List of values.
I wanted to call a method with key and each value of list as parameters to the method. Iterate for all the keys. I am able to do it with two forEach loops as shown in my example below.
I would like know if we can write the same logic using streams and flatMap in Java 8 without forEach inner loop? thanks
Map<String,ArrayList<String>> xhashMap ;
if(xhashMap!=null) {
xhashMap.forEach((k,l)-> {
if(k.equals("ax")){
l.forEach(v->{
method1(v,AA.class);
}
}
if(k.equals("bx")){
l.forEach(v->{
method1(v,BB.class);
}
}
});
}
It doesn't matter whether you use a for loop, forEach or the Stream API. In all cases, you are iterating over a Map to compare each key against a certain value, which is perverting the concept of maps, to associate the key with a value and provide (usually far better that linear) lookup methods.
Further, you should use a Map<String, List<String>> instead, not referring to an implementation type like ArrayList, and not letting it be null in the first place, instead of having it to check for null later-on.
If you follow theses advice, your code becomes
Map<String, List<String>> xhashMap;
// always initialize the map to a non-null reference
xhashMap.getOrDefault("ax", Collections.emptyList())
.forEach(v -> method1(v, AA.class));
xhashMap.getOrDefault("bx", Collections.emptyList())
.forEach(v -> method1(v, BB.class));
If the map is, as the variable name suggests, a hash map, the two lookups will have O(1) time complexity, but even a TreeMap with O(log(n)) complexity will be better than iterating over the map and compare all keys.
As long as the action consists of a sole method invocation with different parameters, there is not much gain in trying to re-use common code, as the sharing code would be much more complicated.
Yes, we can't write it with Stream API, but it's not much better.
Since you are performing side effects and not collecting results, Stream would essentially be:
xhashMap.entrySet()
.stream()
.forEach(e -> ...);
and unfortunately, contain same logic inside the forEach.
Actually, you can even skip Stream creation at this point because you can perform forEach without creating a Stream:
xhashMap.entrySet()
.forEach(e -> ...);
Sure it can be done with flatMap and closures
xHashMap.entrySet().stream()
.flatMap(e -> e.getValue().stream()
.<Runnable>map(v -> () -> {
final String k = e.getKey();
if (k.equals("ax")) {
method1(v, AA.class);
}
if (k.equals("bx")) {
method1(v, BB.class);
}
})
)
.forEach(Runnable::run);
I have a method in a data Structure that I wish to use to pass various collectors and apply them to my object.
The following is the Method -->
public <R> R applyCollector(String key, Collector a)
{
this.key = key;
this.a = a;
R result = (R) this.stateList.stream().
map(state -> state.getKey(key)).collect(a);
return result;
}
The above method basically takes in a "key" and a Collector that it applies over the values got by key.
This is the way I'm using it -->
Collector stringToListCollector =
Collectors.toList();
List<String> values =
myObject.applyCollector("key",
stringToListCollector);
This works fine for simple things like getting count, average etc.
But, what if I wish to send something more complex, like a Nested Collector.
For example, say my "key" returns me a String, which is actually an IP or even an Integer.
What I'd like to do is to send a collector that first Converts my String to Integer by doing a "integer::parseInt" and then doing the toList.
Right now I have to first retrieve a list named values (defined above). And then do a values.stream().Map(Integer::ParseString).collect(Collectors::averagingInt).
I might need to do this operation multiple times, I have two options.
Make the ToList and Map and Collect as a function. and call it. This
beats my purpose of lambda.
Write or nest existing Collectors to directly do that for me. This
option looks neater to me because if I can do it correctly, I'll be
able to do everything in One Pass instead of the 2 passes it takes
me now, as well as maybe save the Memory I need to first create a
list.
How do I do this? Write a Collector that does -->
Gets an object, and runs Integer::ParseInt upon it and then do an
Average Operation.
For your example it would look like
applyCollector("key", mapping(Integer::parseInt, averagingInt(i -> i))
Collectors can be composed to some extend:
Collectors.mapping executes function before collecting
Collectors.collectingAndThen executes function after collecting
additionally some collectors accept downstream collectors i.e groupingBy
I'm still trying to fully grasp working with the Stream package in Java 8 and was hoping for some help.
I have a class, described below, instances of which I receive in a list as part of a database call.
class VisitSummary {
String source;
DateTime timestamp;
Integer errorCount;
Integer trafficCount;
//Other fields
}
To generate some possibly useful information about this, I have a class VisitSummaryBySource which holds the sum total of all visits (for a given timeframe):
class VisitSummaryBySource {
String sourceName;
Integer recordCount;
Integer errorCount;
}
I was hoping to construct a List<VisitSummaryBySource> collection which as the name sounds, holds the list of VisitSummaryBySource objects containing the sum total of records and errors encountered, for each different source.
Is there a way I can achieve this using streams in a single operation? Or do I need to necessarily break this down into multiple operations? The best I could come up with is:
Map<String, Integer> recordsBySrc = data.parallelStream().collect(Collectors.groupingBy(VisitSummaryBySource::getSource,
Collectors.summingInt(VisitSummaryBySource::getRecordCount)));
and to calculate the errors
Map<String, Integer> errorsBySrc = data.parallelStream().collect(Collectors.groupingBy(VisitSummaryBySource::getSource,
Collectors.summingInt(VisitSummaryBySource::getErrorCount)));
and merging the two maps to come up with the list I'm looking for.
You're on the right track. The uses of Collectors.summingInt are examples of downstream collectors of the outer groupingBy collector. This operation extracts one of the integer values from each VisitSummaryBySource instance in the same group, and sums them. This is essentially a reduction over integers.
The problem, as you note, is that you can extract/reduce only one of the integer values, so you have to perform a second pass to extract/reduce the other integer values.
The key is to consider reduction not over the individual integer values but over the entire VisitSummaryBySource object. Reduction takes a BinaryOperator, which takes two instances of the type in question and combines them into one. Here's how to do that, by adding a static method to VisitSummaryBySource:
static VisitSummaryBySource merge(VisitSummaryBySource a,
VisitSummaryBySource b) {
assert a.getSource().equals(b.getSource());
return new VisitSummaryBySource(a.getSource(),
a.getRecordCount() + b.getRecordCount(),
a.getErrorCount() + b.getErrorCount());
}
Note that we're not actually merging the source names. Since this reduction is only performed within a group, where the source names are the same, we assert that we can only merge two instances whose names are the same. We also assume the obvious constructor taking a name, record count, and error count, and call that to create the merged object, containing the sums of the counts.
Now our stream looks like this:
Map<String, Optional<VisitSummaryBySource>> map =
data.stream()
.collect(groupingBy(VisitSummaryBySource::getSource,
reducing(VisitSummaryBySource::merge)));
Note that this reduction produces map values of type Optional<VisitSummaryBySource>. This is somewhat odd; we'll deal with it below. We could avoid the Optional by using another form of the reducing collector that takes an identity value. This is possible but somewhat nonsensical, as there's no good value to use for the source name of the identity. (We could use something like the empty string, but we'd have to abandon our assertion that we merge only objects whose source names are equal.)
We don't really care about the map; it only needs to be kept around long enough to reduce the VisitSummaryBySource instances. Once that's done, we can just pull out the map values using values() and throw away the map.
We can also turn this back into a stream and unwrap the Optional by mapping them through Optional::get. This is safe, because a value never ends up in the map unless there's at least one member of the group.
Finally, we collect the results into a list.
The final code looks like this:
List<VisitSummaryBySource> output =
data.stream()
.collect(groupingBy(VisitSummaryBySource::getSource,
reducing(VisitSummaryBySource::merge)))
.values().stream()
.map(Optional::get)
.collect(toList());
I am trying to change some for-each loops to lambda forEach()-methods to discover the possibilities of lambda expressions. The following seems to be possible:
ArrayList<Player> playersOfTeam = new ArrayList<Player>();
for (Player player : players) {
if (player.getTeam().equals(teamName)) {
playersOfTeam.add(player);
}
}
With lambda forEach()
players.forEach(player->{if (player.getTeam().equals(teamName)) {playersOfTeam.add(player);}});
But the next one doesn't work:
for (Player player : players) {
if (player.getName().contains(name)) {
return player;
}
}
with lambda
players.forEach(player->{if (player.getName().contains(name)) {return player;}});
Is there something wrong in the syntax of the last line or is it impossible to return from forEach() method?
The return there is returning from the lambda expression rather than from the containing method. Instead of forEach you need to filter the stream:
players.stream().filter(player -> player.getName().contains(name))
.findFirst().orElse(null);
Here filter restricts the stream to those items that match the predicate, and findFirst then returns an Optional with the first matching entry.
This looks less efficient than the for-loop approach, but in fact findFirst() can short-circuit - it doesn't generate the entire filtered stream and then extract one element from it, rather it filters only as many elements as it needs to in order to find the first matching one. You could also use findAny() instead of findFirst() if you don't necessarily care about getting the first matching player from the (ordered) stream but simply any matching item. This allows for better efficiency when there's parallelism involved.
I suggest you to first try to understand Java 8 in the whole picture, most importantly in your case it will be streams, lambdas and method references.
You should never convert existing code to Java 8 code on a line-by-line basis, you should extract features and convert those.
What I identified in your first case is the following:
You want to add elements of an input structure to an output list if they match some predicate.
Let's see how we do that, we can do it with the following:
List<Player> playersOfTeam = players.stream()
.filter(player -> player.getTeam().equals(teamName))
.collect(Collectors.toList());
What you do here is:
Turn your input structure into a stream (I am assuming here that it is of type Collection<Player>, now you have a Stream<Player>.
Filter out all unwanted elements with a Predicate<Player>, mapping every player to the boolean true if it is wished to be kept.
Collect the resulting elements in a list, via a Collector, here we can use one of the standard library collectors, which is Collectors.toList().
This also incorporates two other points:
Code against interfaces, so code against List<E> over ArrayList<E>.
Use diamond inference for the type parameter in new ArrayList<>(), you are using Java 8 after all.
Now onto your second point:
You again want to convert something of legacy Java to Java 8 without looking at the bigger picture. This part has already been answered by #IanRoberts, though I think that you need to do players.stream().filter(...)... over what he suggested.
If you want to return a boolean value, then you can use something like this (much faster than filter):
players.stream().anyMatch(player -> player.getName().contains(name));
This what helped me:
List<RepositoryFile> fileList = response.getRepositoryFileList();
RepositoryFile file1 = fileList.stream().filter(f -> f.getName().contains("my-file.txt")).findFirst().orElse(null);
Taken from Java 8 Finding Specific Element in List with Lambda
You can also throw an exception:
Note:
For the sake of readability each step of stream should be listed in new line.
players.stream()
.filter(player -> player.getName().contains(name))
.findFirst()
.orElseThrow(MyCustomRuntimeException::new);
if your logic is loosely "exception driven" such as there is one place in your code that catches all exceptions and decides what to do next. Only use exception driven development when you can avoid littering your code base with multiples try-catch and throwing these exceptions are for very special cases that you expect them and can be handled properly.)