I have a List of Person containing a List of Employee Object. Employee has attribute Name, Age, dept, City. Now I have list of another List. The List of Criteria can be dynamic as it can contain 1 or more criteria's. I want to filter Employee List based on this dynamic List of Criteria so if the type is age then I need to compare age with emp.age and if type is city then i need to compare with emp.city. I am not including getter/setters here. But here is how the classes look:
public class Person {
private List<Employee> empList;
public Person(List<Employee> empList) {
super();
this.empList = empList;
}
public List<Employee> getEmpList() {
return empList;
}
public void setEmpList(List<Employee> empList) {
this.empList = empList;
}
}
public class Employee {
private String name;
private int age;
private String city;
private String dept;
public Employee(String name, int age, String city, String dept) {
super();
this.name = name;
this.age = age;
this.city = city;
this.dept = dept;
}
}
public class Criteria {
private CriteriaType type;
private String value;
public Criteria(CriteriaType type, String value) {
super();
this.type = type;
this.value = value;
}
}
public enum CriteriaType {
NAME, CITY, AGE, DEPT
}
public class FilterEmployee {
public static void main(String[] args) {
Employee emp1 = new Employee("John", 30, "Dallas", "HR");
Employee emp2 = new Employee("Steve", 31, "Austin", "HR");
Employee emp3 = new Employee("Andrew", 25, "Houston", "Finance");
Employee emp4 = new Employee("Mike", 30, "Dallas", "HR");
List<Employee> empList = new ArrayList<Employee>();
empList.add(emp1);
empList.add(emp2);
empList.add(emp3);
empList.add(emp4);
Person personObj = new Person(empList);
Criteria c1 = new Criteria(CriteriaType.CITY, "Dallas");
Criteria c2 = new Criteria(CriteriaType.NAME, "Mike");
List<Criteria> criteriaList = new ArrayList<Criteria>();
criteriaList.add(c1);
criteriaList.add(c2);
//Filter person list containing employee list on the basis of //Criteria list
}
}
Is there a clean way to do the comparison and filtering using java streams?
I modified your code somewhat as adding getters in some classes.
What makes this difficult is that you are handling mixed types (age is numeric and the rest are string. So when you specify your Criteria you need to put age in as a string. I convert it to int when filtering so you don't need to make age a string in the Employee class.
I used a switch to accumulate a boolean
result for the filter.
And here is the `toString() method I used in the Employee class.
public String toString() {
return "%s, %d, %s, %s".formatted(name, age, city, dept);
}
Main code
public static void main(String[] args) {
List<Employee> empList = List.of(
new Employee("John", 30, "Dallas", "HR"),
new Employee("Steve", 31, "Austin", "HR"),
new Employee("Andrew", 25, "Houston", "Finance"),
new Employee("Mike", 30, "Dallas", "HR"));
Criteria c1 = new Criteria(CriteriaType.CITY, "Dallas");
Criteria c2 = new Criteria(CriteriaType.NAME, "Mike");
List<Criteria> criteriaList = Arrays.asList(c1, c2);
List<Employee> filteredList = empList.stream()
.filter(emp -> filterIt(criteriaList, emp)).toList();
filteredList.forEach(System.out::println);
}
public static boolean filterIt(List<Criteria> crt, Employee emp) {
boolean result = true;
for (Criteria c : crt) {
result &= switch (c.getType()) {
case CriteriaType.AGE ->
emp.getAge() == Integer.parseInt(c.getValue());
case CriteriaType.CITY -> emp.getCity().equals(c.getValue());
case CriteriaType.NAME -> emp.getName().equals(c.getValue());
case CriteriaType.DEPT -> emp.getDept().equals(c.getValue());
};
if (!result) { // short circuit if ever false
return result;
}
}
return result;
}
prints
[Mike, 30, Dallas, HR]
for just filtering on Dallas
it prints
John, 30, Dallas, HR
Mike, 30, Dallas, HR
Updated Answer
I added a few "improvements"
in the switch statement, use equalsIgnoreCase
to when comparing strings. This makes the criteria a little more forgiving unless case is important. It can always be retro'd to just equals
I added a options of ALL_OF
or ANY_OF
as an enum to allow requiring all criteria to match or any criteria to match. Here is the new code.
static enum CriteriaType {
NAME, CITY, AGE, DEPT
}
static enum FilterType {ALL_OF(true),ANY_OF(false);
private boolean type;
private FilterType(boolean type) {
this.type = type;
}
public boolean getFilterType() {
return type;
}
}
public static void main(String[] args) {
List<Employee> empList = List.of(
new Employee("John", 30, "Dallas", "Finance"),
new Employee("Steve", 31, "Austin", "HR"),
new Employee("Andrew", 25, "Houston", "Finance"),
new Employee("Mike", 30, "Dallas", "HR"));
Criteria c1 = new Criteria(CriteriaType.DEPT, "Finance");
Criteria c2 = new Criteria(CriteriaType.AGE, "25");
List<Criteria> criteriaList = Arrays.asList(c1, c2);
List<Employee> filteredList = empList.stream()
.filter(emp -> filterIt(criteriaList, emp, FilterType.ANY_OF)).toList();
filteredList.forEach(System.out::println);
}
public static boolean filterIt(List<Criteria> crt, Employee emp,
FilterType filterType) {
boolean finalResult = filterType.getFilterType();
boolean result;
for (Criteria c : crt) {
result = switch (c.getType()) {
case CriteriaType.AGE ->
emp.getAge() == Integer.parseInt(c.getValue());
case CriteriaType.CITY -> emp.getCity().equalsIgnoreCase(c.getValue());
case CriteriaType.NAME -> emp.getName().equalsIgnoreCase(c.getValue());
case CriteriaType.DEPT -> emp.getDept().equalsIgnoreCase(c.getValue());
};
if (filterType == FilterType.ALL_OF) {
//short circuit if false
if (finalResult &= result) {
return false;
}
} else {
// can't short circuit for ANY_OF
finalResult |= result;
}
}
return finalResult;
}
}