14

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.

Willi Mentzel
  • 27,862
  • 20
  • 113
  • 121
mr.M
  • 851
  • 6
  • 23
  • 41
  • 1
    What you would like to do would not compile, and you didn't explain what it's supposed to do. – JB Nizet Aug 23 '15 at 12:12
  • I just gave an example. Sure, it wont compile. Here is what I would like to do: I would like to get a predicate from a map by its string abbreviation and insert a parameter into the taken out from a map predicate. – mr.M Aug 23 '15 at 12:16
  • 1
    What would the parameter be used for? What's the goal of 45 in your example? – JB Nizet Aug 23 '15 at 12:27
  • 45 along with the 56 are the filtering parameters for a give predicate `allPilots` i.e. this predicate should return false if the pilot age is not in the age range (45, 65). – mr.M Aug 23 '15 at 12:32
  • 1
    OK. But what would this parameter mean if you passed "allDrivers" or "allDraftees" instead of "allPilots". You seem to want to contradictor things at the same time: store predicates in a map to be able to one with a string key without caring how it's implemented, and also be able to pass a parameter to customize the predicate, although they all do things differently. – JB Nizet Aug 23 '15 at 12:52
  • @JBNizet, yeah. I do understand that there a contradiction. But, I am currently concerned with the possibility to pass a parameters in such map. – mr.M Aug 23 '15 at 12:59

2 Answers2

14

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);
rooscous
  • 481
  • 2
  • 7
  • 19
JB Nizet
  • 678,734
  • 91
  • 1,224
  • 1,255
  • Exactly what I was looking for. Thank you! – mr.M Aug 23 '15 at 13:57
  • 1
    Given that the OP wants to pass in two int params, shouldn't this be a BiFunction> instead of an IntFunction? – KumarM Aug 23 '15 at 22:03
  • @KumarM I missed the second parameter in the question and only noticed the 45. But yes, if he needs two parameters, the signature of the factory will have to change. – JB Nizet Aug 24 '15 at 05:59
8

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());
        });
Edd
  • 3,724
  • 3
  • 26
  • 33