javajava-stream

filter list based on dynamic criteria using java streams


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?


Solution

  • 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"

     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;
     }
    
    }