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
Related
I have a problem how to stream asynchornously and call a method,
e.g.
List<User> users = List.of(user1, user2, user3);
List<Workplace> worklpaces = List.of(workplace1,workplace2,workplace3)
It's always the same users.size == workplaces.size
we have a function mapping
public List<UserWithWorkplace> combineUserWithWorkplaceAndType(List<User> users,List<Workplace>
worklpaces, Type someRandomtype) {
//here is the problem it wont it should be get
//List<UserWithWorkplace>.size == users.size == workplaces.size
return users.stream().flatMap(user ->
worklpaces.stream()
.map(worklpace -> mapping(user,worklpace, someRandomtype)))
.toList()
}
private UserWithWorkplace mapping( User user, Workplace workplace,Type someRandomtype){
//cominging and returning user with workplace
}
How to achieve that result?
Assuming you want to create pairs of (user, workplace) from two separate users an workplaces streams, this operation is normally called "zipping".
Guava library provide Streams.zip(Stream, Steam, Function) method for this. In your case the code would look like:
Stream<UserWithWorkplace> zipped = Streams.zip(
users.stream(),
worklpaces.stream(),
(u, w) -> this.mapping(u, w, someRandomtype));
However your example code uses List and not Stream to represent data. I'm not sure if you have to use Java streams for this, a simple for loop with i index might be easier.
What you're describing is a zipping operation.
If using Google Guava, you can do this to combine them:
Streams.zip(users.stream(), workplaces.stream(), (user, workplace) -> mapping(user, workplace, someType))
You can also find some other implementations of this operation described here
I have a Java Optional variable that I'm using as below.
Optional<Forms> formsOptional = input.data().get().forms();
if(formsOptional.isPresent()) {
this.doSomething(myValue, forms.get());
} else {
this.doSomething(myValue, Forms.builder().names(myList).build());
}
In this case, doSomething method is a void method that does something. As this is a void method, I'm confused about how I can use the map().orElseGet(), as I don't have anything to transform as well. Any suggestions to optimize this piece of code using any Java8 Optional techniques would be much appreciated.
Use orElseGet(other), so the builder expression doesn't get executed if the value is present.
Optional<Forms> formsOptional = input.data().get().forms();
Forms forms = formsOptional.orElseGet(() -> Forms.builder().names(myList).build());
this.doSomething(myValue, forms);
You would normally do that as part of the previous method chain, which you might split on multiple lines, for clarify, if needed:
Forms forms = input.data()
.get()
.forms()
.orElseGet(() -> Forms.builder().names(myList).build());
this.doSomething(myValue, forms);
You already have the best with java-8 version, but if you are using java-9 or higher you can use ifPresentOrElse
formsOptional.ifPresentOrElse(val -> doSomething(myValue, val),
() -> doSomething(myValue, Forms.builder().names(myList).build()));
Sorry for some kind of theoretical question, but I'd like to find a way of quick reading someone else's functional code, building chain of methods use templates.
For example:
Case 1.
When I see use of .peek method or .wireTap from Spring Integration, I primarily expect logging, triggering monitoring or just transitional running external action, for instance:
.peek(params ->
log.info("creating cache configuration {} for key class \"{}\" and value class \"{}\"",
params.getName(), params.getKeyClass(), params.getValueClass()))
or
.peek(p ->
Try.run(() -> cacheService.cacheProfile(p))
.onFailure(ex ->
log.warn("Unable to cache profile: {}", ex.toString())))
or
.wireTap(sf -> sf.handle(msg -> {
monitoring.profileRequestsReceived();
log.trace("Client info request(s) received: {}", msg);
Case 2.
When I see use of .map method or .transform from Spring Integration, I understand that I'm up to get result of someFunction(input), for instance:
.map(e -> GenerateTokenRs.builder().token(e.getKey()).phoneNum(e.getValue()).build())
or
.transform(Message.class, msg -> {
ErrorResponse response = (ErrorResponse) msg.getPayload();
MessageBuilder builder = some tranforming;
return builder.build();
})
Current case.
But I don't have such a common view to .flatMap method.
Would you give me your opinion about this, please?
Add 1:
To Turamarth: I know the difference between .map and .flatMap methods. I actively use both .map, and .flatMap in my code.
But I ask community for theirs experience and coding templates.
It always helps to study the signature/javadoc of the streamish methods to understand them:
The flatMap() operation has the effect of applying a one-to-many transformation to the elements of the stream, and then flattening the resulting elements into a new stream.
So, typical code I expect, or wrote myself:
return someMap.values().stream().flatMap(Collection::stream)
The values of that map are sets, and I want to pull the entries of all these sets into a single stream for further processing here.
In other words: it is about "pulling out things", and getting them into a stream/collection for further processing.
I've found one more use template for .flatMap.
Let's have a look at the following code:
String s = valuesFromDb
.map(v -> v.get(k))
.getOrElse("0");
where Option<Map<String, String>> valuesFromDb = Option.of(.....).
If there's an entry k=null in the map, then we'll get null as a result of code above.
But we'd like to have "0" in this case as well.
So let's add .flatMap:
String s = valuesFromDb
.map(v -> v.get(k))
.flatMap(Option::of)
.getOrElse("0");
Regardless of having null as map's value we will get "0".
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?
In AssertJ you can do the following to assert the contents of a list:
assertThat(list).extracting("name").containsExactlyInAnyOrder("Alice", "Bob");
I often find myself wanting to do more complex assertions on the elements themselves, e.g., asserting that Alice is a tall brunette and Bob is tiny and bald.
What is the best way to do this using AssertJ?
My own solution is to do:
assertThat(list).extracting("name").containsExactlyInAnyOrder("Alice", "Bob");
list.stream()
.filter(person -> "Alice".equals(person.getName()))
.forEach(alice -> {
assertThat(alice).extracting("size").isEqualTo("tall")
assertThat(alice).extracting("hair").isEqualTo("brunette")
});
list.stream()
.filter(person -> "Bob".equals(person.getName()))
.forEach(bob -> {
assertThat(bob).extracting("size").isEqualTo("tiny")
assertThat(bob).extracting("hair").isNull()
});
or equivalently (java 7) :
assertThat(list).extracting("name").containsExactlyInAnyOrder("Alice", "Bob");
for(Person person : list){
switch (testCase.getName()){
case "Alice":
assertThat(person).extracting("size").isEqualTo("tall")
assertThat(person).extracting("hair").isEqualTo("brunette")
break;
case "Bob":
assertThat(person).extracting("size").isEqualTo("tiny")
assertThat(person).extracting("hair").isNull()
break;
}
}
but I am wondering if there is a better solution.
I like the fact that this solution makes a distinction between the expected elements being in the list and the elements themselves being correct.
For filtering, you can directly use any flavor of filteredOn, then either allMatch or allSatisfy (when I say directly I mean no need to stream your collection in order to filter it).
I suggest to explore AssertJ API, you have other assertions like anySatisfy or using conditions with method like are, areAtLeast, ... the vast majority of the API has javadoc with examples to show how to use it.
Additionally one can have a look at the examples in the assertj-examples project.
Hope it helps
#Test
public void test(){
assertThat(list).filteredOn(person -> person.getName().equals("Alice")).extracting("size").first().isEqualTo("tall");
assertThat(list).filteredOn(person -> person.getName().equals("Alice")).extracting("hair").first().isEqualTo("brunette");
assertThat(list).filteredOn(person -> person.getName().equals("Bob")).extracting("size").first().isEqualTo("tiny");
assertThat(list).filteredOn(person -> person.getName().equals("Bob")).extracting("hair").first().isNull();
}
Often I find it's easier to just express the whole expected collection:
assertThat(list).containsOnlyElementsOf(Arrays.asList(
new Person("Alice", "tall", "brunette"),
new Person("Bob", "tiny", null)
)