38

I can't get it to compile, is it even possible to chain predicate lambdas?

Arrays.asList("1","2","3").stream().filter( (e -> e=="1" ).or(e-> e=="2") ).count();

Or only way is to explicitly create a predicate and then combine like so:

Predicate<String> isOne= e -> e=="1";
Arrays.asList("1","2","3").stream().filter( isOne.or(e -> e=="2") ).count();

Or is there more "functionally elegant" way to achieve same thing?

user1606576
  • 1,062
  • 2
  • 11
  • 11
  • Can you give a better example? Because in the given example, `e -> e == "1" || e == "2"` would be a simpler solution than joining two predicates. – nosid Jun 24 '14 at 21:25
  • 1
    I think the `or` method is for case when you already have one predicate, and you vant to chain it with other. If you are writing both predicates, you can combine them into one direcctly, no need for `or` method. – kajacx Jun 24 '14 at 21:35
  • 1
    Also, as I highlighted in my answer, use `equals()` to compare strings, don't use `==` (unless you are sure that all the strings being compared are interned---which is _not_ the case the vast majority of the time). – C. K. Young Jun 24 '14 at 21:39
  • @nosid, Actually, i don't think i can provide an example that couldn't be resolved using || or && since Predicate has to return boolean. I just locked myself on finding a functional solution. – user1606576 Jun 24 '14 at 22:00
  • @ChrisJester-Young yes i know to use `equals()` and i specifically didn't use it in this code so the part of code i had questions about was clearly visible. – user1606576 Jun 24 '14 at 22:02

6 Answers6

48

You can use:

((Predicate<String>) e -> e.equals("1")).or(e -> e.equals("2"))

but it's not very elegant. If you're specifying the conditions in-line, just use one lambda:

e -> e.equals("1") || e.equals("2")
C. K. Young
  • 219,335
  • 46
  • 382
  • 435
  • 22
    Lambda expressions have no intrinsic type; they are converted to their target type, which must be a functional interface. The problem here is that it doesn't know what target type to use the for first lambda expression. This cacades; it can't know what `or()` method to invoke. The example in this answer shows providing a target type for the first lambda with a cast, which is one of your options for re-injecting the type information you need. – Brian Goetz Jun 24 '14 at 21:35
  • 1
    @BrianGoetz +1 Totally that, and nice to see a comment from the spec lead of JSR 335 himself. :-D On which note, maybe you'd know the answer to [this question](http://stackoverflow.com/q/17203781/13)? :-) – C. K. Young Jun 24 '14 at 21:42
  • @ChrisJester-Young Thanks! second part is obviously the cleanest solution, i just locked myself on finding a solution with combining lambdas and missed the obvious. And first part of your answer is exactly what i was trying to do. – user1606576 Jun 24 '14 at 22:04
  • @BrianGoetz thats exactly the missing piece of theory i needed :)... And to think i stopped crunching through "Java Concurrency in Practice" to play with lambdas and streams :) – user1606576 Jun 24 '14 at 22:16
10

I was working on a similar problem of predicate chaining and came up with the following

public static <T> Predicate<T> chain (Function<T,Predicate<T>> mapFn, T[]args) {
    return Arrays.asList(args)
        .stream()
        .map(x->mapFn.apply(x))
        .reduce(p->false, Predicate::or);
}

The first parameter to chain is a lambda (Function) that returns a lambda (Predicate) so it needs a couple of arrows

public static void yourExample() {
    String[] filterVals = { "1", "2" };

    Arrays.asList("1","2","3")
        .stream()
        .filter(chain(x-> (y-> y.equals(x)), filterVals))
        .count();
}

For comparison, here is what I was trying to achieve...

public static void myExample() {
    String[] suffixes = { ".png", ".bmp" };
    Predicate<String> p = chain (x-> y-> y.endsWith(x), suffixes);
    File[] graphics = new File("D:/TEMP").listFiles((dir,name)->p.test(name));
    Arrays.asList(graphics).forEach(System.out::println);
}
Sarah Phillips
  • 923
  • 11
  • 30
  • 1
    This is the best answer, I adapted your method as a BiFunction like this BiFunction,Function>, Predicate> chainFilter = (args,f)->args.stream().map(s->f.apply(s)) .reduce(pr->false, Predicate::or); – Gabriel Hernandez Feb 26 '19 at 21:12
9

You can use with magic method $:

<T> Predicate<T> $(Predicate<T> p) {
     return p;
}

Arrays.asList("1", "2", "3").stream().filter( $(e -> e=="1").or(e -> e=="2") ).count();
Vitaliy Oliynyk
  • 429
  • 5
  • 3
  • 2
    Why would one bother creating a new method just for that? By the way there is no special meaning for naming this function $. Anyway this works too. – Pikachu Sep 15 '16 at 22:19
  • 5
    By the way there is no special meaning for naming this function $. Yes there is.. if you want your code to be unreadable... – rents Dec 20 '16 at 11:14
  • I've named this method "either" so its usage reads like this : `either(e -> e=="1").or(e -> e=="2")` – bowmore Jun 08 '17 at 09:14
  • @bowmore, too bad..your `either` method does also have a `and` method – KitKarson Dec 26 '17 at 05:26
  • @KitKarson That is true, but that hasn't bothered me so far, as I tend to chain ands with additional filter calls on the stream itself. If it really bothers you, you could return a wrapper class around `Predicate` to hide the `and()`. – bowmore Dec 26 '17 at 06:26
5

This is just a small addition to @Chris Jester-Young answer. It is possible to make the expression shorter by using method reference:

((Predicate<String>) "1"::equals).or("2"::equals)
Anton Balaniuc
  • 10,889
  • 1
  • 35
  • 53
4
Arrays.asList("1","2","3").stream().filter( Arrays.asList("1", "2")::contains).count();

and yes method "either" is good idea

    public static void main(String[] args) {
        long count = Arrays.asList("1","2","3").stream().filter(either("1"::equals).or("2"::equals)).count();
        System.out.println(count);
    }

    private static <T> Predicate<T> either(Predicate<T> predicate) {
        return predicate;
    }

or you can use import static java.util.function.Predicate.isEqual; and write isEqual("1").or(isEqual("2"))

Matt Watson
  • 5,065
  • 4
  • 30
  • 48
1
Predicate<String> pred1 =  "1"::equals;
Predicate<String> pred2 =  "2"::equals;

public void tester(){
      Arrays.asList("1","2","3").stream().filter(pred1.or(pred2)).count();
}

You can move your separate your conditions to make it possible to recombine them other ways.