2

How can I use additional variables inside of named Java Lambda Expressions, as I can do in an anonymous one? I found a workaround (method returns a Lambda), but out of curiosity: can this be formulated in pure Lambda syntax as well? I can't find an answer in the usual tutorials and references.

Please consider the following example code:

Arrays.asList(1,2,3,4,5).stream().filter( i -> i<3 ).toArray();

I can give a name to this anonymous Lambda:

Arrays.asList(1,2,3,4,5).stream().filter(LessThanThree).toArray();
[...]
Predicate<Integer> LessThanThree = i -> i<3;

But if I want to use a variable instead of the constant, the same syntax will not work. because I can't find a way to declare parameters to the named Lambda:

Edit: Thank you, dpr, for hinting that this is a matter of scope! I enhanced to following block of code to try to clarify what I'm interested in.

filterIt(3);
[...]
void filterIt(int x) {
    Arrays.asList(1,2,3,4,5).stream().filter(LessThanX).toArray();
}
[...]
Predicate<Integer> LessThanX = i -> i<x; // won't compile!

The workaround seems to be a method that returns the Lambda:

private Predicate<Integer> lessThanX(int x) {
    return i -> i<x;
}

But: is there a way to formulate this in pure named Lambda?

3
  • Of course final variables are possible. A mix of functional, side-effect free programming and using variables is not advisable. Commented Sep 14, 2017 at 9:32
  • 3
    Actually your code compiles. At least if you declare the lambda and x in the same scope. Commented Sep 14, 2017 at 9:33
  • It works when x is either a local variable that is (effectively) final, or when x is a field. The latter is probably what you want. Commented Sep 14, 2017 at 10:02

5 Answers 5

4

In your case, you could use a java.util.function.BiPredicate instead of the normal predicate

BiPredicate<Integer, Integer> lessThan = (i, j) -> i < j;
Arrays.asList(1,2,3,4,5).stream().filter(i -> lessThan.test(i, 3)).collect(Collectors.toList());

If you require tests with more than 2 inputs you might need to write your own @FunctionalInterface that you can use as lambda.

Sign up to request clarification or add additional context in comments.

2 Comments

I didn't notice there was a BiPredicate until now (which is why I used a BiFunction in my answer). +1
@Eran I was actually surprised that there is no TriPredicate ;) Apart from the fixed return type of the BiPredicate my answer is of course essentially the same as your's.
1

If you add a second variable (such as x in your example), you no longer have a Predicate<Integer>. You have a BiFunction<Integer,Integer,Boolean> (i.e. a function with two parameters that returns a Boolean).

You can define a lambda expression and assign it to a BiFunction<Integer,Integer,Boolean>:

BiFunction<Integer,Integer,Boolean> LessThanX = (i,x) -> i<x;

In order to convert it to a Predicate<Integer>, you'll need a second lambda expression that relies on the first one:

Predicate<Integer> LessThan3 = i -> LessThanX.apply (i, 3);

Of course you can use the BiFunction directly:

Arrays.asList(1,2,3,4,5).stream().filter(i -> LessThanX.apply (i, 3)).toArray();

Comments

1

Be aware that this lambda: a -> a < b takes only one parameter - a. The value of x is baked into the function.

When you use this in a loop:

for(int b = 0; b<20; b++) {
   IntStream.range(15,25)
        .filter( a -> a < b)
        ...;
}

You're creating a new predicate each time around the b loop:

  a -> a < 0
  a -> a < 1
  a -> a < 2

... and so on. In this example if b were not effectively final the compiler would forbid it.

You can do exactly the same thing by defining a local Predicate:

for(int b = 0; b<20; b++) {
   Predicate<Integer> lessThanB = a -> a < b;
   IntStream.range(15,25)
        .filter(lessThanB)
        ...;
}

But you can't define that lessThanB outside the scope in which b is a final variable.

You can define a BiPredicate:

BiPredicate<Integer,Integer> lessThan = (a,b) -> a < b;

... but you can't use BiPredicate everywhere you can use Predicate - e.g. in Stream.filter(). Currying turns a BiPredicate into a Predicate with one of the parameters baked in:

final int x = 5;
Predicate<Integer> lessThanX = n -> lessThan.test(n,x);

... and you can do this inline in a filter():

.filter( n -> lessThan.test(n,x))

However you've not gained much -- you're still creating a new Predicate for each new value of x.


There is no TriPredicate but a principle of FP is that a chain of n single-param functions is equivalent to one n-param function. You don't need more than one parameter (BiPredicate is a courtesy)

That is, instead of:

TriFunction<String, String, String, User> finder = (company, dept, name) -> { ...}`

... you'd have:

Function<String, Function<String, Function<String, User>>> format =
      company -> dept -> name -> 
          String.format("Company: %s\nDept: %s\nName: %s", company, dept, name);

To be called as String s = format.apply("Daimler").apply("Accounts").apply("John Doe")

...or less generally, a three-param Predicate would be:

Function<String, Function<String, Predicate<String>>> userExists = 
    company -> dept -> name ->  somethingThatReturnsBoolean(company, dept, name);

... called as:

boolean exists = 
    userExists.apply("Daimler").apply("Accounts").test("John Doe")

Comments

0

This code compiles!

int x = 3;
Predicate<Integer> LessThanThree = i -> i<x;
Arrays.asList(1,2,3,4,5).stream().filter(LessThanThree).toArray();

keep in mind that x has to be effectively final! as soon as you add another line like x = 4 it breaks.

Comments

0

Your snippet already works perfect and it should.

        List<Integer> vals = Arrays.asList(1,2,3,4,5);
        int compare_int = 2;
        //Add this compare_int = 4;//Variable used in lambda expression should be final or effectively final
        Predicate<Integer> GT_3 = integer -> integer > compare_int;
        System.out.println(vals.stream().filter(GT_3).count());//prints 3

I prefer it to be a method than being a variable coz, the method gives more of a meaning to the operation or the comparison. Ofcourse for this small case of checking greater than doesn't have impact, but a real case scenario, methods are better and it also allows you to share across within the class's methods.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.