Search code examples
javagenericsreflectionjava-8predicate

How to dynamically create a generic lambda implementation by type?


For example, i have class

public class Human {

  private String name;

  ...
}

and i want to implement something like this:

(1)

List<Human> humans =  initHumans();
Equals<Human> humanEquals = new Equals<>();
Predicate<Human> filter = humanEquals.filter("name", "John");
List<Human> filteredHumans = humans
    .stream()
    .filter(filter)
    .collect(Collectors.toList());

Equals:

public class Equals<T> extends AbstractPredicate<T> {

  public java.util.function.Predicate<T> filter(String fieldName, String fieldValue) {
    ....
  }  
}

is it posible implement filter method to provide the (1) behavior?

I want to return a Predicate like this:

Predicate<Human> predicate = human -> human.getName().equals("John");

similarly should work for other classes:

Predicate<Car> filter = humanEquals.filter("color", "red");
//like this:
Predicate<Car> predicate= human -> human.getColor().equals("red");

Solution

  • Yes, this might be achieved by reflection:

    public static <T> Predicate<T> filter(Class<T> clazz, String fieldName, Object fieldValue) {
        // 1
        return (T instance) -> {
            try {
                final Field field = clazz.getDeclaredField(fieldName);
                field.setAccessible(true);
    
                return fieldValue.equals(field.get(instance));
            } catch (NoSuchFieldException | IllegalAccessException e) {
                // 2
            }
            return false;
        };
    }
    
    • I made a method static because I have no idea what the AbstractPredicate is and why you need to create an instance of a utility class.
    • I am getting the value from a field directly without using a getter - what naming convention should we adhere? (it might be improved)

    The use is:

    final Predicate<Human> filter = Equals.filter(Human.class, "name", "John");
    
    System.out.println(filter.test(new Human("John")));     // true
    System.out.println(filter.test(new Human("Andrew")));   // false
    

    There are still a few questions we need to think of - validating parameters (1), handling exceptions (2).


    Another option can be using a Function<T, E> to provide a reference to a getter:

    public static <T, E> Predicate<T> filter(Function<T, E> supplier, E value) {
        return (T instance) -> supplier.apply(instance).equals(value);
    }
    

    An example of use:

    final Predicate<Human> predicate = Equals.filter(Human::getName, "John");
    
    System.out.println(predicate.test(new Human("John")));    // true
    System.out.println(predicate.test(new Human("Andrew")));  // false