Skip to content
Advertisement

Correct syntax for making a functional interface instance automatically call its method

I’ve been watching Douglas Schmidt classes on Parallel Java. He introduces Lambda x method referencing syntax discussion, highlighting how the last one is preferable, as it makes clearer what the code is actually doing, not what the programmer is trying to do with the code, even more than forEach approach.

String[] names = {"first", "Second", "third"};
Arrays.sort(names, (n1,n2) -> n1.compareToIgnoreCase(n2));
Arrays.sort(names, String::compareToIgnoreCase); //preferable

For example, that approach mitigates the chances of programmer making mistakes inside lambda function: passing the wrong argument, inverting arguments order, adding collateral effects, etc.

Then he introduces Functional interfaces, an interface that contains only an abstract method, implementing its own interface runTest with an abstract method factorial():

private static <T> void runTest(Function<T,T> factorial, T n) {
        System.out.println(n+ " factorial = " + factorial.apply(n));
    }
    
    private static class ParallelStreamFactorial{
        static BigInteger factorial(BigInteger n) {
            return LongStream
                    .rangeClosed(1, n.longValue())
                    .parallel()
                    .mapToObj(BigInteger::valueOf)
                    .reduce(BigInteger.ONE, BigInteger::multiply);
        }
    }

Calling it with the following syntax:

import java.math.BigInteger;
import java.util.function.Function;
import java.util.stream.LongStream;

public static void main(String[] args) {
        BigInteger n = BigInteger.valueOf(3);
        runTest(ParallelStreamFactorial::factorial, n);     
    }

The code works and prints

3 factorial = 6

As I’m studying lambdas, I tried to interchange method reference syntax for lambda syntax, and managed to using:

public static void main(String[] args) {
        BigInteger n = BigInteger.valueOf(3);
        runTest((number)->ParallelStreamFactorial.factorial(number), n);
    }

Which also worked.

Then he proceeds to explain built-in interfaces, such as Predicate<T>{boolean test(T t);}, and that’s where I got stuck.

I managed to implement a Predicate<Integer> that tests if the integer is bigger than 0 using the three syntaxes:

  1. Instantiating an object myPredicate from a class that implements Predicate<Integer>
  2. Instantiating an object lambdaPredicate from a lambda
  3. Instantiating an object methodReferencePredicatefrom a method reference:
import java.util.function.Function;
import java.util.function.Predicate;

public class MyPredicates {
    
    public static void main(String[] args) {
        
        Predicate<Integer> constructorPredicate = new  myPredicate();
        System.out.println(constructorPredicate.test(4));
        
        Predicate<Integer> lambdaPredicate = (number)-> number > 0;
        System.out.println(lambdaPredicate.test(4));
        
        Predicate<Integer> methodReferencePredicate = myMethodReference::myTest;
        System.out.println(methodReferencePredicate.test(4));

    }
    
    private static class myPredicate implements Predicate<Integer>{
        public boolean test(Integer t) {
            return t>0;
        }
    }
    
    private static class myMethodReference{
        public static boolean myTest(Integer t) {
            return t>0;
        }
    }
}

And then calling their .test() methods. They’re all three working and printing true.

However I would like to “instantiate and call” everything in a single line, as he did in his example. It seems like his code is inferring the type of the argument passed (I may be wrong) but it’s definitely running automatically.

I tried different things:

Predicate<Integer>(myMethodReference::myTest, 4);
Predicate(myMethodReference::myTest, 4);
Predicate<Integer>((number) -> myMethodReference.myTest(number), 4);
Predicate((number) -> myMethodReference.myTest(number), 4);

But none of them work.

They throw:

Syntax error, insert ";" to complete LocalVariableDeclarationStatement

and

The method Predicate(myMethodReference::myTest, int) is undefined for the type MyPredicates

Errors. I also don’t even know the name of what he’s doing in that single line to properly search better on internet for references.

What’s the correct syntax for that, whether by method reference or lambdas?

Advertisement

Answer

You’ve made things far too complicated.

There is no point in lambdas if you want to ‘execute them immediately’.

Here is how you run your my test code ‘immediately’:

System.out.println(number > 4);

Why mess with lambdas? They just make matters confusing here.

The very point of a lambda is two-fold:

  1. A way to transmit code itself to other contexts.
  2. Control flow abstraction.

In java in particular, option 2 is an evil – it makes code ugly, harder to reason about, introduces pointless distractions, and in general should be avoided… unless you’re employing it to avoid an even greater evil. That happens plenty – for example, a reasonable ‘stream chain’ is generally better even though its control flow abstraction. I’d say this:

int total = list.stream()
  .filter(x -> x.length() < 5)
  .mapToInt(Integer::valueOf)
  .sum();

is the lesser evil compared to:

int total = 0;
for (var x : list) {
  if (x.length() < 5) continue;
  total += Integer.parseInt(x);
}

but it is a pretty close call.

Why is it ‘evil’? Because lambdas in java are non transparent in 3 important ways, and this non-transparency is a good thing in the first case, but a bad thing in the second. Specifically, lambdas are not transparent in these ways:

  1. Lambdas cannot change or even read local variables from outer scope unless they are (effectively) final.
  2. Lambdas cannot throw checked exceptions even if the outer scope would handle them (because they catch them or the method you’re in declared throws ThatException).
  3. Lambdas cannot do control flow. You can’t break, continue, or return from within a lambda to outside of it.

These 3 things are all useful and important things to be doing when you’re dealing with basic control flow. Therefore, lambdas should be avoided as you create a bunch of problems and inflexibility by using them… unless you’ve avoided more complexity and inflexibility of course. It’s programming: Nothing is ever easy.

The notion of bundling up code is therefore much more useful, because those non-transparencies turn into upside:

  1. If you take the lambda code and export it to someplace that runs that code much later and in another thread, what does it even mean to modify a local variable at that point? The local variable is long gone (local vars are ordinarily declared on stack and disappear when the method that made them ends. That method has ended; your lambda survived this, and is now running in another context). Do we now start marking local vars as volatile to avoid thead issues? Oof.

  2. The fact that the outer code deals with a checked exception is irrelevant: The lexical scope that was available when you declared the lambda is no longer there, we’ve long ago moved past it.

  3. Control flow – breaking out of or restarting a loop, or returning from a method. What loop? What method? They have already ended. The code makes no sense.

See? Lambda lack of transparency is in all ways great (because they make no sense), if your lambda is ‘travelling’. Hence, lambdas are best used for this, they have no downsides at that point.

Thus, let’s talk about travelling lambdas: The very notion is to take code and not execute it. Instead, you hand it off to other code that does whatever it wants. It may run it 2 days from now when someone connects to your web server, using path /foobar. It may run every time someone adds a new entry to a TreeSet in order to figure out where in the tree the item should be placed (that’s precisely the fate of the lambda you pass to new TreeSet<X>((a, b) -> compare-a-and-b-here).

Even in control flow situations (which are to be avoided if possible), your lambda still travels, it just travels to place that does immediately ends up using it, but the point of the lambda remains control flow abstraction: You don’t run the code in it, you hand your lambda off to something else which will then immediately run that 0 to many times. That’s exactly what is happening here:

list.forEach(System.out::println);

I’m taking the code notion of System.out.println(someString), and I don’t run it – no, I bundle up that idea in a lambda and then pass this notion to list’s forEach method which will then invoke it for me, on every item in the list. As mentioned, this is bad code, because it needlessly uses lambdas in control flow abstraction mdoe which is inferior to just for (var item : list) System.out.println(item);, but it gets the point across.

It just doesn’t make sense to want to write a lambda and immediately execute it. Why not just… execute it?

In your example from the book, you don’t actually execute the lambda as you make it. You just.. make it, and hand it off to the runTest method, and it runs it. The clue is, runTest is a method (vs your attempts – Predicate is not a method), it’s not magical or weird, just.. a method, that so happens to take a Function<A, B> as argument, and the lambda you write so happens to ‘fit’ – it can be interpreted as an implementation of Function<A, B>, and thus that code compiles and does what it does.

You’d have to do the same thing.

But, if that code is a single-use helper method, then there’s no point to the lambda in the first place.

User contributions licensed under: CC BY-SA
5 People found this is helpful
Advertisement