Java reflection inside stream's filter

Viewed 1164

I have an Java POJO:

public class Event {
    private String id;
    private String name;
    private Long time;
}

A simple filtering method I created is:

public static List<Event> simpleFilter(List<Event> eventList, String value) {
    return eventList.stream().filter(Event -> Event.getName().equals(value)).collect(Collectors.toList());
}

Now my task is to create a generic method instead of simpleFilter which can be applied for any Java POJO object and any of its fields. For example, if in future there is a new Java object Employee and we want to filter on its String field employeeDepartment, we can use same generic filter method by passing the List of Java object (List<Employee>, Class type Employee.class, which field (getEmployeeDepartment) and what value ("Computer") we want to filter on.

I created a method definition:

public static <T> List<T> genericStringFilterOnList(List<T> list, Class<T> c, String methodName, String value) {
}

Caller looks like:

//events is List<Event>
//getName is the method in Event on which I want to filter
//e2 is value which I want to filter
genericStringFilterOnList(events, Event.class, "getName", "e2")

My implementation is:

public static <T> List<T> genericStringFilterOnList(List<T> list, Class<T> c, String methodName, String value) {
    return list.stream().filter(m -> {
        try {
            return c.getMethod(methodName, null).invoke(c).equals(value);
        } catch (IllegalAccessException e) {
        } catch (IllegalArgumentException e) {
        } catch (InvocationTargetException e) {
        } catch (NoSuchMethodException e) {
        } catch (SecurityException e) {
        }
        return false;
    }).collect(Collectors.toList());
}

All these catch were generated by IDE because of checked exception. This doesn't seem to working because it is returning back an empty list. What I am trying to do here is - Using the class type (which is Event.class), I am getting method name using reflection and then invoking that method and then invoke which is basically calling getName() method of Event class and then equals. I also tried this -

return c.getMethod(methodName, null).invoke(c.newInstance()).equals(value);

But with this I am getting NPE on this

}).collect(Collectors.toList());

Can you please help me in creating a generic method which can be called for a List of any POJO and a filter can be applied on any of its String type methods?

1 Answers

The invocation should happen on the steamed item, not on the class itself. The method Method::invoke(Object obj, Object... args) has two parameters:

  • obj - the object the underlying method is invoked from
  • args - the arguments used for the method call

Change the line:

return c.getMethod(methodName, null).invoke(c).equals(value);

.. to:

return c.getMethod(methodName, null).invoke(m).equals(value);

The confusion comes from the one-lettered variable names (see the solution below).


The whole solution shall be simplified. You don't want to extract the very same Method through reflection for the each object present in the stream pipeline. Extract the Method first and reuse it:

static <T> List<T> genericStringFilterOnList(List<T> list, Class<T> clazz, String method, String value) {
    try {
        // reflection invoked just once
        Method method = clazz.getMethod(method, null);
        // now streaming of the n items
        return list.stream().filter(item -> {
            try { 
                return value.equals(method.invoke(item));
            } catch (IllegalAccessException | InvocationTargetException e) {}
            return false;
        }).collect(Collectors.toList());
    } catch (NoSuchMethodException e) {}
    return Collections.emptyList();
}

(Out of scope of this question): Note there is a higher probability that the invoked method returns null than the passed value is, therefore I'd go for value.equals(method.invoke(item)). Maybe, you want to add some additional comparison condition when both values are null to be compared.

Related