Java 8: Stream and filter based on optional conditions - java-8

Example: Filter a list of products that have a price based on fromPrice and toPrice. They could either both be supplied, or just one.
Find all products whose price is greater than fromPrice
Find all products whose price is less than toPrice
Find all products whose price is between fromPrice and toPrice
Product:
public class Product {
private String id;
private Optional<BigDecimal> price;
public Product(String id, BigDecimal price) {
this.id = id;
this.price = Optional.ofNullable(price);
}
}
PricePredicate:
public class PricePredicate {
public static Predicate<? super Product> isBetween(BigDecimal fromPrice, BigDecimal toPrice) {
if (fromPrice != null && toPrice != null) {
return product -> product.getPrice().isPresent() && product.getPrice().get().compareTo(fromPrice) >= 0 &&
product.getPrice().get().compareTo(toPrice) <= 0;
}
if (fromPrice != null) {
return product -> product.getPrice().isPresent() && product.getPrice().get().compareTo(fromPrice) >= 0;
}
if (toPrice != null) {
return product -> product.getPrice().isPresent() && product.getPrice().get().compareTo(toPrice) <= 0;
}
return null;
}
}
Filters:
return this.products.stream().filter(PricePredicate.isBetween(fromPrice, null)).collect(Collectors.toList());
return this.products.stream().filter(PricePredicate.isBetween(null, toPrice)).collect(Collectors.toList());
return this.products.stream().filter(PricePredicate.isBetween(fromPrice, toPrice)).collect(Collectors.toList());
Is there a way to improve my Predicate instead of having the if not null checks? Anything that can be done with optionals?

No, Optional is not designed to replace null checks.
But your code can be improved by avoiding duplication, and by avoiding to return null (which is clearly not a valid value for a Predicate) if both arguments are null:
public static Predicate<Product> isBetween(BigDecimal fromPrice, BigDecimal toPrice) {
Predicate<Product> result = product -> true;
if (fromPrice != null) {
result = result.and(product -> product.getPrice().isPresent() && product.getPrice().get().compareTo(fromPrice) >= 0);
}
if (toPrice != null) {
result = result.and(product -> product.getPrice().isPresent() && product.getPrice().get().compareTo(toPrice) <= 0);
}
return result;
}

You can use Apache Commons Lang, it offers null safe comparison:
ObjectUtils.compare(from, to)
null is assumed to be less than a non-value

Related

Not able to filter elements using an enum

protected static double averagePrice( List<ComputerComponent> list ) {
return list.stream()
// .filter( line-> EnumUtils.isValidEnum(ComputerComponentCategory.class, line.getCategory()) )
// .filter( line-> isInEnum( line.getCategory(), ComputerComponentCategory.class) )
// .filter( line-> inEnum(line.getCategory(),EnumUtils.getEnumMap(ComputerComponentCategory.class ).keySet() ))
.filter( line ->
line.getCategory().contains("CPU")
|| line.getCategory().contains("GPU")
|| line.getCategory().contains("Monitor")
|| line.getCategory().contains("Keyboard")
|| line.getCategory().contains("Mouse")
|| line.getCategory().contains("Storage")
|| line.getCategory().contains("Memory"))
.mapToDouble(ComputerComponent::getPrice)
.average()
.orElseThrow(NoSuchElementException:: new);
}
I have an enum as
public enum ComputerComponentCategory {
CPU("CPU"),
MONITOR("Monitor"),
KEYBOARD("Keyboard"),
MOUSE("Mouse"),
GPU("GPU"),
MEMORY("Memory"),
STORAGE("Storage"),
NULL("NOT DEFINED");
private String label;
ComputerComponentCategory(String label) {
this.label = label;
}
public String getLabel() {
return this.label;
}
public static ComputerComponentCategory getValue(String label) {
switch(label) {
case "CPU":
return CPU;
case "Monitor":
return MONITOR;
case "Keyboard":
return KEYBOARD;
case "Mouse":
return MOUSE;
case "GPU":
return GPU;
case "Memory":
return MEMORY;
case "Storage":
return STORAGE;
default:
return NULL ;
}
}
}
I pass a list of ComputerComponent class to the averagePrice() function which has two fields of price
which is of type double and
category which is of type String.
My list has 4 elements with categories as "CPU", "Mouse",
"Keyboard" and "Storage" with their respective prices as 34.0, 155.0, 23.0 and 75.0.
When I try to use inEnum(), isInEnum() or EnumUtils.isValidEnum() functions, I get the average price
as 34.0 which I think that they just return the price of the first element rather than the average.
But when I do filtering using
.filter( line ->
line.getCategory().contains("CPU")
|| line.getCategory().contains("GPU")
|| line.getCategory().contains("Monitor")
|| line.getCategory().contains("Keyboard")
|| line.getCategory().contains("Mouse")
|| line.getCategory().contains("Storage")
|| line.getCategory().contains("Memory"))
I get the correct average value of 71.75.
The implementations that I have used for isInEnum() and inEnum() functions are the following:
public static <E extends Enum<E>> boolean isInEnum(String value, Class<E> enumClass) {
for (E e : enumClass.getEnumConstants()) {
if(e.name().contains(value)) { return true; }
}
return false;
}
public static boolean inEnum ( String category, Set<String> value ) {
for(String s: value ) {
if ( category.contains(s) ) {
return true ;
}
}
return false ;
}
How can I use enums correctly with java streams to filter by valid category names and get the correct
average value of price?
What mistake I am making when using streams and its functions ?
You could simply use your ComputerCategoryValue.getValue method and check for null, given the category of line:
public class EnumTest {
#Test
public void testBothMethods() {
final ComputerComponent c1 = new ComputerComponent(ComputerComponentCategory.CPU.getLabel(), 12.21);
final ComputerComponent c2 = new ComputerComponent(ComputerComponentCategory.MEMORY.getLabel(), 23.45);
final List<ComputerComponent> list = Arrays.asList(c1, c2);
assertEquals(averagePriceWithFilter(list), averagePriceWithInEnum(list), 0.01);
}
protected static double averagePriceWithFilter(final List<ComputerComponent> list) {
return list.stream()
.filter(line -> line.getCategory().contains("CPU")
|| line.getCategory().contains("GPU")
|| line.getCategory().contains("Monitor")
|| line.getCategory().contains("Keyboard")
|| line.getCategory().contains("Mouse")
|| line.getCategory().contains("Storage")
|| line.getCategory().contains("Memory"))
.mapToDouble(ComputerComponent::getPrice)
.average()
.orElseThrow(NoSuchElementException::new);
}
protected static double averagePriceWithInEnum(final List<ComputerComponent> list) {
return list.stream()
.filter(line -> ComputerComponentCategory.getValue(line.getCategory()) != null)
.mapToDouble(ComputerComponent::getPrice)
.average()
.orElseThrow(NoSuchElementException::new);
}
}
EDIT: explaining your mistakes:
EnumUtils.getEnumMap(ComputerComponentCategory.class).keySet()) returns a map of the enum name (not its label), so that the check will only work for CPU as there name and label are the same.
Same for the other method!
You need to use getLabel() instead of name() or use equalsIgnoreCase instead of contains.

map has list as value check if null

I have a Map in Java 8.
I need to check if the list that comprises the map value is empty or null and return the result .
I have tried likewise with no luck
public boolean mapValuesEmpty() {
boolean result = true;
for (Entry<Integer, List<SomeObjectName>> entry : eventLogsMap.entrySet()) {
if (entry.getValue() != null) {
result = false;
break;
}
}
return result;
}
Many thanks
boolean result = eventLogsMap
.values()
.stream()
.anyMatch(list -> list != null && !list.isEmpty())

Spring Cache Key Using Arguments

I have a class Person with properties id, name and age.
I would like to cache Person object using id and name.
my method is
#Cacheable(value = "person", key = "#p.id + p.name")
getPerson(Person p).
Question is, how do i use cache annotation on getPerson()... something like this.
Using the annotation you could concatenate the values to create a key (I read but have not tested tha the debug symbols may be removed and so the parameter should be referenced as "p0").
#Cacheable(value="person", key="#p0.id.concat(‘:’).concat(#p0.name)")
Otherwise, it will be cached based on Person class equals() and hashCode() just the same way as if you were using the Person object as a key in a Map.
So, for example:
public class Person {
String id;
String name;
Number age;
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (!(obj instanceof Person))
return false;
Person other = (Person) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}

Comperator caused "Comparison method violates its general contract!" - 1300 items sort

I have data of 1300 items, sorted with my comperator. Sorting is working fine when I'm using JAVA 6.
When project is run on JAVA 7 I'm getting this exception:
env: JAVA 7, Vaadin 6.8.12, tested with both 32 bit and 64 bit same error occured. ( It is working fine on JAVA 6 )
Caused by: java.lang.IllegalArgumentException: Comparison method violates its general contract!
at java.util.TimSort.mergeLo(TimSort.java:747)
at java.util.TimSort.mergeAt(TimSort.java:483)
at java.util.TimSort.mergeCollapse(TimSort.java:410)
at java.util.TimSort.sort(TimSort.java:214)
at java.util.TimSort.sort(TimSort.java:173)
at java.util.Arrays.sort(Arrays.java:659)
at java.util.Collections.sort(Collections.java:217)
at com.vaadin.data.util.AbstractInMemoryContainer.doSort(AbstractInMemoryContainer.java:575)
at com.vaadin.data.util.AbstractInMemoryContainer.sortContainer(AbstractInMemoryContainer.java:555)
at com.vaadin.data.util.AbstractBeanContainer.sort(AbstractBeanContainer.java:440)
at com.vaadin.ui.CustomTable.sort(CustomTable.java:4552)
This is comperator which I'm using:
private class StudyRecordComparator implements Comparator<Object> {
#Override
public int compare(Object o1, Object o2) {
if (o1 instanceof String && o2 instanceof String) {
return ((String) o1).compareToIgnoreCase(((String) o2));
}
else if (o1 instanceof QuestionnaireStatusType && o2 instanceof QuestionnaireStatusType) {
QuestionnaireStatusType status1 = (QuestionnaireStatusType) o1;
QuestionnaireStatusType status2 = (QuestionnaireStatusType) o2;
if(status1.equals(status2)) {
return 0;
}
switch(status1) {
case WAITING_FOR_REVIEW :
return -1;
case IN_REVIEW :
if(status2.equals(QuestionnaireStatusType.WAITING_FOR_REVIEW)) {
return 1;
} else {
return -1;
}
case WAITING_PUBLICATION :
if(status2.equals(QuestionnaireStatusType.WAITING_FOR_REVIEW) || status2.equals(QuestionnaireStatusType.IN_REVIEW)) {
return 1;
} else {
return -1;
}
case PUBLISHED :
if(status2.equals(QuestionnaireStatusType.WITHDRAWN)) {
return -1;
} else {
return 11;
}
case WITHDRAWN :
return 1;
}
}
else if (o1 instanceof Date && o2 instanceof Date) {
return ((Date) o1).compareTo(((Date) o2));
} else if (o1 instanceof Integer && o2 instanceof Integer) {
return ((Integer) o1).compareTo(((Integer) o2));
} else if (o1 instanceof User && o2 instanceof User) {
return ((User)o1).toString().compareToIgnoreCase(((User)o2).toString());
}
return 0;
}
}
public enum QuestionnaireStatusType {
IN_PROGRESS("In progress"),
WAITING_FOR_REVIEW("Waiting for review"),
IN_REVIEW("In review"),
WAITING_PUBLICATION("Waiting for publication"),
PUBLISHED("Published"),
WITHDRAWN("Withdrawn");
private final String field;
public String getField() {
return field;
}
QuestionnaireStatusType(String field){
this.field = field;
}
}
Does your collection contain null?
If so, there is one problem with your comparator: It always returns 0 for null, so null is considered equal to everything.
As a result for A > B (premise), you will also have A == null and null == B so by transitivity A and B should also be equal, which violates the premise.
You need to establish a total and consistent ordering for all possible values (including null if that is allowed).
The same issue occurs when your collection contains mixed types (some Strings, some Dates, some QuestionnaireStatusType).

Filtering whole of hierarchical list with LINQ

I have a simple hiearchical list of objects "ProductGroups" (IQueryable).
Each "ProductGroup" has a collection of ProductGroups called "Children",an integer ParentId field and a boolean "IsEnabled" field.
public class ProductGroup
{
public int Id;
public int ParentId;
public ICollection<ProductGroup> Children;
public bool IsEnabled;
}
I want to be able to return a tree of ProductGroups where "IsEnabled" is true.
At the moment, if I do
ProductGroups.Where(x => x.IsEnabled)
This returns the enabled products. If I do
ProductGroups.Where(x => x.ParentId == null)
This returns the roots. I want to be able to return a full tree excluding the disabled items in the neatest way possible (i.e. not using for loops after querying the collection).
ProductGroup1 (IsEnabled == true)
|
--------- ProductGroup2 (IsEnabled == true)
| |
| ----------- ProductGroup4 (IsEnabled == false)
|
--------- ProductGroup4 (IsEnabled == false)
i.e. Return ProductGroup1 with 1 child ProductGroup2
Thanks
I'm not sure of your exact needs with the filtered collection. I'm looking at it as a View-Model. And a recursive extension method provides you with a neat way to achieve this filter:
public static class Helper
{
public static ICollection<ProductGroup> EnabledWithChildren(this ICollection<ProductGroup> source)
{
var result = new List<ProductGroup>();
foreach (var ele in source)
{
if(ele.IsEnabled == true)
{
var productGroup = new ProductGroup{Id = ele.Id, IsEnabled = ele.IsEnabled, ParentId = ele.ParentId};
if(ele.Children != null && ele.Children.Count() > 0)
productGroup.Children = ele.Children.EnabledWithChildren();
result.Add(productGroup);
}
}
return result;
}
}
And usage:
public class ProductGroup
{
public int Id;
public int? ParentId;
public ICollection<ProductGroup> Children;
public bool IsEnabled;
static void Main()
{
var filteredProductGroups = ProductsGroups.EnabledWithChildren();
}
}

Resources