I have the following snippet that collects specific objects which have a name string property that contains a specific filter filterName.
List<Foo> filteredFoo= fooList.stream()
.filter(Objects::nonNull)
.filter(myFoo -> {
if (Strings.isNullOrEmpty(myFoo.getName()))
return false;
return myFoo.getName().contains(filterName);
}
).collect(Collectors.toList());
It works as expected but I was wondering whether is there a more elegant way to write the if-statement in a functional way and check for empty or null properties in a nicer fashion than having the conditional block in the filter.
Replace second filter with following:
.filter(myFoo -> Optional.ofNullable(myFoo.getName())
.filter(n -> n.contains(filterName))
.isPresent())
or even:
.filter(myFoo -> {
String name = myFoo.getName();
return name != null && name.contains(filterName)
})
Go for the functional style, for the result expression:
.filter(foo -> foo.getName() != null && foo.getName().contains(filterName))
Splitting would not bring more simplicity:
.filter(foo -> foo.getName() != null)
.filter(foo -> foo.getName().contains(filterName))
Using predicates on Foo::getName (Objects::isNull) is senseless complicated too, just in order to spare a variable.
If filterName is not empty itself, Strings.isEmptyOrNull is not needed.
If you have access to Foo Class then move the if conditions to a method isSameName and use filter as below
filter(myFoo -> {return myFoo.isSameName(filterName);})
Related
I'm trying to filter two conditions in a stream that is inside another stream of data, what I need to do is to check if the object is there by using the "name" parameter and then reviewing the Boolean property of "isGoldplated" if is true, I tried using this code but didn't work as it wasn't filtering by the isGoldplated parameter:
List<CompressorModel> filteredCompressors = pack.getSet().getCompressors().stream()
.peek(p -> goldData.stream().map(GoldPlateData::getCompressorSerialNo).anyMatch(name -> name.equals(p.getGcsn())))
.peek(p -> goldData.stream().map(GoldPlateData::getIsGoldplated))
.collect(Collectors.toList());
so I finished using two loops instead:
List<CompressorModel> filteredCompressors = new ArrayList<>();
for (CompressorModel cmp : pack.getSet().getCompressors()) {
for(GoldPlateData gold: goldData) {
if( StringUtils.equalsIgnoreCase(cmp.getGcsn(), gold.getCompressorSerialNo()) && Boolean.TRUE.equals(gold.getIsGoldplated())) {
filteredCompressors.add(cmp);
}
}
}
so my request is, how could I convert these two loops into a working stream?
thanks in advance
You could use filter() on the pack.getSet().getCompressors() stream and then look for a match in goldData, like this:
List<CompressorModel> filteredCompressors = pack.getSet()
.getCompressors().stream()
.filter(cmp -> goldData.stream().anyMatch(gd -> cmp.getGcsn().equalsIgnoreCase(gd.getCompressorSerialNo()) && gd.getIsGoldplated()))
.collect(Collectors.toList());
Peek is basically there only to be used for debugging purposes. Peek can be not executed at all at times because it's a terminal operation. You can see here to get an idea.
So you may modify your implementation to use filter instead.
List<CompressorModel> filteredCompressors = pack.getSet().getCompressors().stream()
.filter(p -> goldData.stream().map(GoldPlateData::getCompressorSerialNo).anyMatch(name -> name.equals(p.getGcsn())))
.filter(p -> goldData.stream().map(GoldPlateData::getIsGoldplated))
.collect(Collectors.toList());
I have a Java 8 stream expression that has 3 filters and works fine.
I want to guard against null pointer exceptions within the filters for most of the values.
This is the expression:
if(!purchasedTripSegments.isEmpty()) {
filteredList = purchasedTripSegments.stream()
.filter(segment -> PurchasedVendorType.RAIL.equals(segment.getVendorType()))
.filter(distinctByKeys(segment -> Arrays.asList(segment.getBillingMethod(),
segment.getOrigin().getNumberCode(), segment.getDestination().getNumberCode(),
segment.getStopOff().getStopOffLocation().getNumberCode())))
.filter(segment -> segment.getBillingMethod().equalsIgnoreCase(BILLING_METHOD_LOCAL) ||
(segment.getBillingMethod().equalsIgnoreCase(BILLING_METHOD_RULE) &&
segment.getDestination().getNumberCode() !=
segment.getStopOff().getStopOffLocation().getNumberCode()))
.collect(Collectors.toList());
}
So the VendorType cannot be null.
So the first filter will be fine.
The 2nd and 3rd filters can have nulls.
The objects (Origin, Destination, StopOff, StopOffLocation) can be null.
And the values (BillingMethod, NumberCode) can be null.
Is there a way to ignore the filter if any of the values in the filter are nulls?
I tried adding .filter(Objects::nonNull)
I have a test case that has a null destination object and the NullPointerException is thrown.
UPDATE
I updated the billingMethod. But I am not clear on how to use Optional to avoid the null checks.
Optional<List<PurchasedTripSegment>> filteredList = Optional.ofNullable(new ArrayList<>());
if(!purchasedTripSegments.isEmpty()) {
filteredList = purchasedTripSegments.stream()
.filter(segment -> PurchasedVendorType.RAIL.equals(segment.getVendorType()))
.filter(distinctByKeys(segment -> Arrays.asList(segment.getBillingMethod(),
segment.getOrigin().getNumberCode(),
segment.getDestination().getNumberCode(),
segment.getStopOff().getStopOffLocation().getNumberCode())))
.filter(segment -> BILLING_METHOD_LOCAL.equals(segment.getBillingMethod())
|| (BILLING_METHOD_RULE.equals(segment.getBillingMethod()) &&
segment.getDestination().getNumberCode() !=
segment.getStopOff().getStopOffLocation().getNumberCode()))
.collect(Collectors.toList());
}
I'm not sure how to apply the changes you suggested to my filter. I tried adding as written but the map() was not recognized.
The middle filter would be the most difficult.
How to check the objects and values for each segment?
UPDATE
As per the comment below implementing a Utility method using Optional.
private Optional<Integer> getDestinationCode(PurchasedCostTripSegment purchasedCostTripSegment) {
return Optional.ofNullable(purchasedCostTripSegment.getDestination()) // empty for 'null'
.map(Destination::getNumberCode);
}
I do a null check for the incoming parameter.
I get an error that the method getNumberCode is not recognized.
The attributes such as the billingMethod, whenever it is possibly null inside the List, it should still work for comparison to get distinct values.
On the other hand, comparing them with some other String constant can be solved in the manner the user FilipRistic suggested.
But, when it is about objects which could be possibly null and you want to access the inner attributes further down safely, you can make use of Optional and chain the accessors. For a sample amongst those, while you want to access the numberCode of your destination which could possibly be null, you can have an accessor in PurchasedTripSegment class to expose this:
Optional<Integer> getDestinationCode() {
return Optional.ofNullable(this.getDestination()) // empty for 'null'
.map(Node::getNumberCode);
}
With similar changes for other accessors, your overall code would update and change to something like:
filteredList = purchasedTripSegments.stream()
.filter(segment -> PurchasedVendorType.RAIL.equals(segment.getVendorType()))
.filter(distinctByKey(segment -> Arrays.asList(segment.getBillingMethod(),
segment.getOriginCode(), segment.getDestinationCode(),
segment.getStopOffLocationCode())))
.filter(segment -> segment.getBillingMethod().equalsIgnoreCase(BILLING_METHOD_LOCAL) ||
(segment.getBillingMethod().equalsIgnoreCase(BILLING_METHOD_RULE) &&
segment.getDestinationCode().equals(segment.getStopOffLocationCode())))
.collect(Collectors.toList());
No there isn't any way for filter to know that since it doesn't know in which way you will use element inside Predicate, your only solution is to perform the check for null yourself.
Note that you can avoid check in cases where you are comparing to constant that you know isn't null, instead of writing:
segment.getBillingMethod().equalsIgnoreCase(BILLING_METHOD_LOCAL)
You could write it like this:
BILLING_METHOD_LOCAL.equalsIgnoreCase(segment.getBillingMethod())
This will avoid NPE but it only helps you in few cases not all of them, for other cases you will have to perform check or maybe refactor to return type Optional and your condition could look something like this:
segment.getDestination()
.flatMap(d -> segment.getStopOff()
.map(s -> s.getStopOffLocation)
.filter(s -> s.getNumberCode() == d.getNumberCode()) )
.isPresent();
I have a list of 'Blocks' that could be null. Each of the blocks contains a list of names that also could be null. I'm trying to find if any of the names in all the blocks is the word "blurb".
I have the following code which does work:
private boolean containsText(List<Blocks> blocks) {
return Optional.ofNullable(blocks)
.map(Collection::stream)
.map(o -> o.map(Blocks::getNames))
.map(e -> e.anyMatch(names -> names != null && names.contains("blurb")))
.orElse(false);
}
But since getNames could return null I have to check for it in the next statement. I could wrap it in another Optional at that point, but then I would end up with an
Optional<Stream<Optional<List<String>>>>
Using names -> names != null
seems cleaner? Or is there a way to simplify this?
With Java-9 you can use Stream.ofNullable as follows:
public boolean containsText(List<Blocks> blocks) {
return Stream.ofNullable(blocks)
.flatMap(b -> b.stream().map(Blocks::getNames).filter(Objects::nonNull))
.anyMatch(s -> s.contains("blurb"));
}
Another simpler variant without the use of Optional would just be :
private boolean containsText(List<Blocks> blocks) {
return blocks != null && blocks.stream()
.map(Blocks::getNames)
.filter(Objects::nonNull)
.anyMatch(list -> list.contains("blurb"));
}
Note: One of your problems is the design where you say, "I have a list of 'Blocks' that could be null", you should fix it to return an empty list for such representation and then get rid of the null check in the above code.
I would not recommend using Optional for null check you can use ternary operator to check blocks is null and simply return false if it is null, else stream the blocks list and check for string
return Objects.isNull(blocks) ? false : blocks.stream()
.map(Blocks::getNames)
.filter(Objects::nonNull)
.anyMatch(list->list.contains("blurb"));
You can also add simple null for existing code
private boolean containsText(List<Blocks> blocks) {
return Optional.ofNullable(blocks)
.map(Collection::stream)
.map(o -> o.map(Blocks::getNames).filter(Objects::nonNull))
.map(e -> e.anyMatch(names -> names.contains("blurb")))
.orElse(false);
}
Is there a 'best practice' for mutating elements within a Stream? I'm specifically referring to elements within the stream pipeline, not outside of it.
For example, consider the case where I want to get a list of Users, set a default value for a null property and print it to the console.
Assuming the User class:
class User {
String name;
static User next(int i) {
User u = new User();
if (i % 3 != 0) {
u.name = "user " + i;
}
return u;
}
}
In java 7 it'd be something along the lines of:
for (int i = 0; i < 7; i++) {
User user = User.next(i);
if(user.name == null) {
user.name = "defaultName";
}
System.out.println(user.name);
}
In java 8 it would seem like I'd use .map() and return a reference to the mutated object:
IntStream.range(0, 7)
.mapToObj(User::next)
.map(user -> {
if (user.name == null) {
user.name = "defaultName";
}
return user;
})
//other non-terminal operations
//before a terminal such as .forEach or .collect
.forEach(it -> System.out.println(it.name));
Is there a better way to achieve this? Perhaps using .filter() to handle the null mutation and then concat the unfiltered stream and the filtered stream? Some clever use of Optional? The goal being the ability to use other non-terminal operations before the terminal .forEach().
In the 'spirit' of streams I'm trying to do this without intermediary collections and simple 'pure' operations that don't depend on side effects outside the pipeline.
Edit: The official Stream java doc states 'A small number of stream operations, such as forEach() and peek(), can operate only via side-effects; these should be used with care.' Given that this would be a non-interfering operation, what specifically makes it dangerous? The examples I've seen reach outside the pipeline, which is clearly sketchy.
Don't mutate the object, map to the name directly:
IntStream.range(0, 7)
.mapToObj(User::next)
.map(user -> user.name)
.map(name -> name == null ? "defaultName" : name)
.forEach(System.out::println);
It sounds like you're looking for peek:
.peek(user -> {
if (user.name == null) {
user.name = "defaultName";
}
})
...though it's not clear that your operation actually requires modifying the stream elements instead of just passing through the field you want:
.map(user -> (user.name == null) ? "defaultName" : user.name)
It would seem that Streams can't handle this in one pipeline. The 'best practice' would be to create multiple streams:
List<User> users = IntStream.range(0, 7)
.mapToObj(User::next)
.collect(Collectors.toList());
users.stream()
.filter(it -> it.name == null)
.forEach(it -> it.name = "defaultValue");
users.stream()
//other non-terminal operations
//before terminal operation
.forEach(it -> System.out.println(it.name));
I've got Google Guava inside Stream:
this.map.entrySet().stream()
.filter(entity -> !Strings.isNullOrEmpty(entity.getValue()))
.map(obj -> String.format("%s=%s", obj.getKey(), obj.getValue()))
.collect(Collectors.joining(","))
As you see there is a statement !String.isNullOrEmpty(entity) inside the filter function.
I don't want to use Guava anymore in the project, so I just want to replace it simply by:
string == null || string.length() == 0;
How can I do it more elegant?
In java 11 there is a new method Predicate::not.
So you can filter out empty string :
list.stream()
.filter(Objects::nonNull)
.filter(Predicate.not(String::isEmpty))
You can write your own predicate:
final Predicate<Map.Entry<?, String>> valueNotNullOrEmpty
= e -> e.getValue() != null && !e.getValue().isEmpty();
Then just use valueNotNullOrEmpty as your filter argument.
If you prefer to use commons-lang3, StringUtils has
isEmpty()
isNotEmpty()
isBlank()
isNotBlank()
These methods can be used in filters as method references:
this.stringList.stream().filter(StringUtils::isNotBlank);
or as lambdas:
this.stringList.stream().filter(s -> StringUtils.isNotBlank(s));
You can create your own Strings class with your own predicate:
public class Strings {
public static boolean isNotNullOrEmpty (String str) {
return str != null && !str.isEmpty();
}
}
Then in your code:
.filter(Strings::isNotNullOrEmpty)
But as #fge mentionned, you can't use that on a Map.Entry<?,?>...
You can break down the filter into two steps:
this.map.entrySet().stream()
.filter(entity -> entity.getValue() != null)
.filter(entity -> !entity.getValue().isEmpty())
.map(obj -> String.format("%s=%s", obj.getKey(), obj.getValue()))
.collect(Collectors.joining(","))
On a side note, most Map.Entry.toString() implementations do exactly what you're doing in map(), so you could theoretically just do map(Map.Entry::toString). But I wouldn't rely on that unless you're producing a toString() or something that doesn't require documented or deterministic behavior.
Also, I know you want to abandon Guava, but here's a solution that might make you reconsider:
Joiner.on(',').withKeyValueSeparator("=")
.join(Maps.filterValues(map, Predicates.not(Strings::isNullOrEmpty)));
It works for me: list.stream().filter(el-> el != null && !el.toString().trim().isEmpty()).collect(Collectors.toList());