Search code examples
javalambdafilterjava-streampredicate

How to chain Predicate<T> in filter() while using method references?


I have a class Movie like this:

public class Movie {    
    private int id, duration, publishingYear;
    private String title, translation;


    public Movie(int id, int duration, int publishingYear, String title, String translation) {
        super();
        this.id = id;
        this.duration = duration;
        this.publishingYear = publishingYear;
        this.title = title;
        this.translation = translation;
    }

    public Predicate<Movie> titleFilter(String filter){
        return m -> m.getTitle().toLowerCase().contains(filter.toLowerCase());
    }

    public Predicate<Movie> translationFilter(String filter){
        return m -> m.getTranslation().toLowerCase().contains(filter.toLowerCase());
    }

}

How to chain Predicate filters while using method references? I understand that method references can be seen as shorthand for lambdas calling only a specific method. In book I'm reading, it says: "...method reference lets you create a lambda expression from an existing method implementation...". So, why I can't do this?

ArrayList<Movie> filteredMovies = (ArrayList<Movie>) app.getMovies().stream()
                .filter((Movie::titleFilter(titleFilter)).or(Movie::translationFilter(translationFilter)))
                .collect(Collectors.toList());

I get that this will work:

Predicate<Movie> titlePredicate = ( m -> m.getTitle().toLowerCase().contains(titleFilter)); 

Predicate<Movie> translationPredicate = ( m -> m.getTitle().toLowerCase().contains(translationFilter)); 

ArrayList<Movie> filteredMovies = (ArrayList<Movie>) app.getMovies().stream()
                                    .filter(titlePredicate.or(translationPredicate))
                                    .collect(Collectors.toList());

As well as this (just insert into filter() ):

public boolean tFilter(String filter){
        return this.getTranslation().toLowerCase().contains(filter.toLowerCase());
    }

I've recently started with lambdas and Stream API. In our curriculum at university there's no mention of streams. So I recon I start by myself. Newbie.


Solution

  • You can't do that because this:

    Movie::titleFilter(titleFilter)
    

    isn't really a method reference. You are kind of passing in a parameter, which seems to indicate that you want to call the method. But you are using ::, which makes it look like a method references.

    If you just did Movie::titleFilter however, that would be a method reference, but it can't be converted to Predicate<Movie>. It could be converted to Function<String, Predicate<Movie>> if you had specified the type explicitly, but that's obviously not what you want.

    Also note that even if you had two methods in Movie like this (these are both convertible to Predicate<Movie>):

    public boolean predicate1() { ... }
    
    public boolean predicate2() { ... }
    

    You cannot or them like this:

    Movie::predicate1.or(Movie::predicate2)
    

    Because Movie::predicate1 doesn't have a type in and of itself. This is to allow it to be converted to any functional interface with a compatible signature. You have to do:

    ((Predicate<Movie>)Movie::predicate1).or(Movie::predicate2)
    

    instead.

    What I think you intended is:

    // note the static modifiers
    public static Predicate<Movie> titleFilter(String filter){
        return m -> m.getTitle().toLowerCase().contains(filter.toLowerCase());
    }
    
    public static Predicate<Movie> translationFilter(String filter){
        return m -> m.getTranslation().toLowerCase().contains(filter.toLowerCase());
    }
    

    And:

    // note that I am calling the methods which *return* a Predicate<Movie>
    ArrayList<Movie> filteredMovies = (ArrayList<Movie>) app.getMovies().stream()
                    .filter((Movie.titleFilter(titleFilter)).or(Movie.translationFilter(translationFilter)))
                    .collect(Collectors.toList());
    

    Basically, to make a method reference, you can't pass parameters to it, and there's not such thing as "partial application" of a method in Java. (It'd be cool if it existed though...)