javareflectioncastingjava-17functional-interface

Is there an implicit type casting when using Functional Interfaces in Java?


I wrote the following Java code and expected it to not compile but it did and also executed counterintuitively. I am using Java 17.

TestFunctionExecutor.java

@FunctionalInterface
public interface TestFunctionExecutor{
    void execute();
}

TestClass.java

public class TestClass{
    public static void main(String... args) {
        TestClass test = new TestClass();
        test.wrapper(test::sampleFunction);
    }
    
    public void sampleFunction() {
        System.out.println("Inside sampleFunction");
    }

    public void wrapper(TestFunctionExecutor method) {
        System.out.println("Before method execution");
        method.execute();
        System.out.println("After method execution");
    }
}

Output -

Before method execution
Inside sampleFunction
After method execution

I thought since wrapper expects an argument of type TestFunctionExecutor and I am passing one of type TestClass the compilation should fail. I used a debugger and looks like method is a TestClass$$Lambda$1... at runtime. This confuses me and I have a few questions -

  1. What is the type of test::SampleFunction? Is it not TestClass or something like TestClass$$sampleFunction...? I am unable to deduce this with a debugger.
  2. Why were there no errors here? Looks like the types somehow became compatible, how?
  3. How does execute know what code to execute?
  4. Is this good code? My aim is to wrap a function so that some code runs before and after it.

Thanks!


Solution

  • In Java, lambda expressions are just a sugar syntax for something called Anonymous class, which are basically classes created on the spot with a name created by the JVM.

    Your code can be equally written in 4 ways (from more concise to less concise) :

    test.wrapper(test::sampleFunction);
    
    test.wrapper(() -> test.sampleFunction());
    
    test.wrapper(() ->  {
       test.sampleFunction();
    });
    
    test.wrapper(new TestFunctionExecutor() {
        public void execute() {
           test.sampleFunction();
        }
    });
    

    When you use a lambda expression, you actually create a new instance of an anonymous class that implements the functional interface. And the method of this implementation contains the run you want to run.

    As for the last question, it might be correct code, but this is a very broad topic that depends.