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?

kaba
  • 362
  • 1
  • 13
  • Of course final variables are possible. A mix of functional, side-effect free programming and using variables is not advisable. – Joop Eggen Sep 14 '17 at 09:32
  • 3
    Actually your code compiles. At least if you declare the lambda and x in the same scope. – dpr Sep 14 '17 at 09: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. – Joop Eggen Sep 14 '17 at 10:02

5 Answers5

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.

dpr
  • 10,591
  • 3
  • 41
  • 71
  • I didn't notice there was a `BiPredicate` until now (which is why I used a `BiFunction` in my answer). +1 – Eran Sep 14 '17 at 09:44
  • @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. – dpr Sep 14 '17 at 09:46
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();
Eran
  • 387,369
  • 54
  • 702
  • 768
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")
slim
  • 40,215
  • 13
  • 94
  • 127
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.

Absurd-Mind
  • 7,884
  • 5
  • 35
  • 47
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.

Karthik R
  • 5,523
  • 2
  • 18
  • 30