Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pass parameters to Predicates

I have the following map of the search criteria:

private final Map<String, Predicate> searchMap = new HashMap<>();

private void initSearchMap() {
    Predicate<Person> allDrivers = p -> p.getAge() >= 16;
    Predicate<Person> allDraftees = p -> p.getAge() >= 18
            && p.getAge() <= 25
            && p.getGender() == Gender.MALE;
    Predicate<Person> allPilots = p -> p.getAge() >= 23
            && p.getAge() <=65;

    searchMap.put("allDrivers", allDrivers);
    searchMap.put("allDraftees", allDraftees);
    searchMap.put("allPilots", allPilots);
}

I am using this map in the following way:

pl.stream()
    .filter(search.getCriteria("allPilots"))
    .forEach(p -> {
        p.printl(p.getPrintStyle("westernNameAgePhone"));
    });

I would like to know, how can I pass some parameters into the map of predicates?

I.e. I would like to get predicate from a map by its string abbreviation and insert a parameter into the taken out from a map predicate.

pl.stream()
    .filter(search.getCriteria("allPilots",45, 56))
    .forEach(p -> {
        p.printl(p.getPrintStyle("westernNameAgePhone"));
    });

Here is the link from I googled out this map-predicate approach.

like image 976
mr.M Avatar asked Aug 23 '15 12:08

mr.M


People also ask

What is a predicate method?

The predicate is a predefined functional interface in Java defined in the java. util. Function package. It helps with manageability of code, aids in unit-testing, and provides various handy functions.

Is predicate a functional interface?

This is a functional interface and can therefore be used as the assignment target for a lambda expression or method reference. Represents a predicate (boolean-valued function) of one argument. This is a functional interface whose functional method is test(Object) .

What is the difference between a predicate integer and an IntPredicate?

Java IntPredicate interface is a predicate of one int-valued argument. It can be considered an operator or function that returns a value either true or false based on certain evaluation on the argument int value. IntPredicate is a functional interface whose functional method is boolean test(int a) .


2 Answers

As I understand it, what you want to do is override some parameters in a named Predicate, but I think this will lead to confusion. If I'm asking for the predicate that determines if somebody is eligible to be a pilot, I don't want to have to worry about knowing what the requirements are at the time when I call it.

Generating predicates on the fly using a Map would be tricky - I'd think the nicest way would be to instead have a Map<String, PredicateFactory>, where the PredicateFactory is some sort of complicated class that generates predicates given some parameters:

Map<String, PredicateFactory<T>> predicates = new HashMap<>();

public Predicate<T> get(String name, Object... params) {
    return predicates.get(name).apply(params);
}

To my mind, the better way to generate "dynamic" predicates is with static methods:

public class PredicatesQuestion {

    public static void main(String[] args) {
        List<Person> people = Arrays.asList(new Person(20, Gender.MALE),
                new Person(45, Gender.FEMALE), new Person(50, Gender.MALE),
                new Person(65, Gender.MALE));

        people.stream()
                .filter(personIsBetweenAges(16, 25))
                .forEach(person -> {
                    System.out.println(person.getAge() + ", " + person.getGender());
                });
    }

    private static Predicate<Person> personIsMale() {
        return person -> person.getGender() == Gender.MALE;
    }

    private static Predicate<Person> personIsBetweenAges(int lower, int upper) {
        return personIsAtLeast(lower).and(personIsYoungerThan(upper));
    }

    private static Predicate<Person> personIsAtLeast(int age) {
        return person -> person.getAge() >= age;
    }

    private static Predicate<Person> personIsYoungerThan(int age) {
        return person -> person.getAge() < age;
    }
}

It's then trivial to create descriptive predicates as required:

private static Predicate<Person> personIsOfDrivingAge() {
    return personIsAtLeast(17);
}

private static Predicate<Person> couldBePilot() {
    return personIsBetweenAges(23, 65).and(personIsMale());

}

And the sort of thing you're trying to achieve with overriding some parameters remains clear with a bit of composition:

people.stream()
        .filter(couldBePilot().and(personIsBetweenAges(45, 56)))
        .forEach(person -> {
            System.out.println(person.getAge() + ", " + person.getGender());
        });
like image 90
Edd Avatar answered Oct 13 '22 07:10

Edd


It seems that what you want is not to store a predicate in a Map. What you want is to be able to store something in a map that is able to create a Predicate<Person> from an int parameter. So what you want is something like this:

Map<String, IntFunction<Predicate<Person>>> searchMap = new HashMap<>();

You would fill it that way:

searchMap.put("allPilots", maxAge -> 
    (p -> p.getAge() >= 23
     && p.getAge() <= maxAge));

And you would use it like this:

Predicate<Person> allPilotsAgedLessThan45 = 
    searchMap.get("allPilots").apply(45);

Of course, it would be clearer if you created your own functional interface:

@FunctionalInterface
public MaxAgePersonPredicateFactory {
    Predicate<Person> limitToMaxAge(int maxAge);
}

You would still fill the map the same way, but you would then have slightly more readable code when using it:

Predicate<Person> allPilotsAgedLessThan45 = 
    searchMap.get("allPilots").limitToMaxAge(45);
like image 28
JB Nizet Avatar answered Oct 13 '22 09:10

JB Nizet