javarecordlambda-metafactory

java.lang.invoke.LambdaConversionException: Incorrect number of parameters for instance method invokeVirtual


package edu.project5;

import java.lang.invoke.*;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import static java.lang.invoke.MethodType.methodType;

public class LambdaExample {

    public static void main(String[] args) throws Throwable {
        // Define a simple record class
        record Person(String name, int age) {
        }

        // Create a LambdaMetafactory
        MethodHandles.Lookup lookup = MethodHandles.lookup();
        MethodHandle methodHandle = lookup.findVirtual(Person.class, "name", methodType(String.class));

        CallSite callSite = LambdaMetafactory.metafactory(
            lookup,
            "get",
            MethodType.methodType(Supplier.class, Person.class),
            MethodType.methodType(String.class, Person.class),
            methodHandle,
            MethodType.methodType(String.class)
        );
        Person person = new Person("John", 30);

        MethodHandle factory = callSite.getTarget();
        Supplier<String> nameExtractor = (Supplier<String>) factory.invokeExact(person);

        // Create an instance of the record
        System.out.println("Extracted Name: " + nameExtractor.get());
    }

}

Hello, for some reason I can't make a lambda with which will call the name of a person and return it to me, I don't quite understand how it works.


Solution

  • I had to read the documentation a few times before I fully understood what arguments need to be passed.

    This part was the clearest explanation, for me:

    When the target of the CallSite returned from this method is invoked, the resulting function objects are instances of a class which implements the interface named by the return type of factoryType, declares a method with the name given by interfaceMethodName and the signature given by interfaceMethodType.

    So, the factoryType argument is what produces the Supplier. The code you have written is correct; it takes a Person and produces a String:

    MethodType.methodType(Supplier.class, Person.class),
    

    The next argument, interfaceMethodType, must be the signature of Supplier.get(). get() returns a value but takes no arguments, so you need to change this:

    MethodType.methodType(String.class, Person.class),
    

    to this:

    MethodType.methodType(Object.class),
    

    Why Object and not String? Because generics are a compiler trick. The get() method of Supplier always returns Object (just like, say, List.get(int) always returns Object). The compiler enforces the proper casting and type checking in order to simulate generic type safety.

    Your MethodHandle is correct. Regarding the final argument to metafactory, the documentation says:

    dynamicMethodType - The signature and return type that should be enforced dynamically at invocation time. In simple use cases this is the same as interfaceMethodType.

    So, once again we can pass MethodType.methodType(Object.class) for that argument.

    The whole thing looks like this:

    CallSite callSite = LambdaMetafactory.metafactory(
        lookup,
        "get",
        MethodType.methodType(Supplier.class, Person.class),
        MethodType.methodType(Object.class),
        methodHandle,
        MethodType.methodType(Object.class)
    );
    

    As Holger pointed out, the last argument is actually the dynamic binding of the returned type, so we can specify that we expect it to be String:

    CallSite callSite = LambdaMetafactory.metafactory(
        lookup,
        "get",
        MethodType.methodType(Supplier.class, Person.class),
        MethodType.methodType(Object.class),
        methodHandle,
        MethodType.methodType(String.class)
    );