Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to convert lambda filters with dynamic values to method references

I have some Java code which filters a list based on some input. It currently uses a lambda, for example:

public List<ComplexObject> retrieveObjectsFilteredByTags(List<String> allowedTags) {
    List<ComplexObject> complexObjects = retrieveAllComplexObjects();
    return complexObjects
                .stream()
                .filter( compObject -> allowedTags.contains(compObject.getTag()))
                .collect(Collectors.toList());
}

What I want to do is to move the filter logic to another method to make it re-usable and easily unit testable. So I wanted to use a method reference in place of the lambda passed to the filter method. Easy to do if the filter logic is fairly static (i.e. list of allowed tags is known at compile time) but I can't figure out how to do this with dynamic data in the filter.

What I wanted was some way to use a method reference and then pass the second dynamic param i.e.

public List<ComplexObject> retrieveObjectsFilteredByTags(List<String> allowedTags) {
    List<ComplexObject> complexObjects = retrieveAllComplexObjects();
    return complexObjects
                .stream()
                .filter(this::filterByAllowedTags, allowedTags)
                .collect(Collectors.toList());
}

So is it possible to do what I want or am I possibly approaching this situation incorrectly?

like image 882
bmooney Avatar asked Oct 29 '14 15:10

bmooney


People also ask

How do you convert lambda expression to method?

If you are using a lambda expression as an anonymous function but not doing anything with the argument passed, you can replace lambda expression with method reference. In the first two cases, the method reference is equivalent to lambda expression that supplies the parameters of the method e.g. System.

What is the difference between a method reference and a lambda expression?

Lambda expression is an anonymous method (method without a name) that has used to provide the inline implementation of a method defined by the functional interface while a method reference is similar to a lambda expression that refers a method without executing it.


1 Answers

I'd suggest passing in a Predicate as a parameter. That way the caller can filter based on any criteria it wants, including allowedTags or whatever:

public List<ComplexObject> retrieveObjectsFilteredBy(Predicate<ComplexObject> pred) {
    List<ComplexObject> complexObjects = retrieveAllComplexObjects();
    return complexObjects.stream()
        .filter(pred)
        .collect(Collectors.toList());
}

This would be called like so:

    List<String> allowedTags = ... ;
    List<ComplexObject> result =
        retrieveObjectsFilteredBy(cobj -> allowedTags.contains(cobj.getTag()));

But you could go even further, depending on how much refactoring you're willing to do. Instead of "retrieve" returning a List, how about having it return a Stream? And instead of the retrieve-filter method returning a List, how about having it return a Stream too?

public Stream<ComplexObject> retrieveObjectsFilteredBy2(Predicate<ComplexObject> pred) {
    Stream<ComplexObject> complexObjects = retrieveAllComplexObjects2();
    return complexObjects.filter(pred);
}

And the calling side would look like this:

    List<String> allowedTags = ... ;
    List<ComplexObject> result =
        retrieveObjectsFilteredBy2(cobj -> allowedTags.contains(cobj.getTag()))
            .collect(toList());

Now if you look at it carefully, you can see that the retrieve-filter method isn't adding any value at all, so you might just as well inline it into the caller:

    List<String> allowedTags = ... ;
    List<ComplexObject> result =
        retrieveAllComplexObjects2()
            .filter(cobj -> allowedTags.contains(cobj.getTag()))
            .collect(toList());

Of course, depending upon what the caller wants to do, it might not want to collect the results into a list; it might want to process the results with forEach(), or something else.

Now you can still factor out the filter into its own method, for testing/debugging, and you can use a method reference:

boolean cobjFilter(ComplexObject cobj) {
    List<String> allowedTags = ... ;
    return allowedTags.contains(cobj.getTag());
}

    List<ComplexObject> result =
        retrieveAllComplexObjects2()
            .filter(this::cobjFilter)
            .collect(toList());

If you don't want the filter to have the allowed tags built into it, you can change it from being a predicate into a higher-order function that returns a predicate instead:

Predicate<ComplexObject> cobjFilter(List<String> allowedTags) {
    return cobj -> allowedTags.contains(cobj.getTag());
}

    List<String> allowedTags = ... ;
    List<ComplexObject> result =
        retrieveAllComplexObjects2()
            .filter(cobjFilter(allowedTags))
            .collect(toList());

Which of these variations makes the most sense depends on what your application looks like and what kind of dynamicism you require in filtering.

like image 164
Stuart Marks Avatar answered Nov 15 '22 04:11

Stuart Marks