`I'm trying to fetch the execution time of a getName() method of Person's class by using LambdaMetaFactory instead of using AOP to improve the performance.
But Facing below exception when tried with the code mentioned:
Exception in thread "main" java.lang.AbstractethodError : Receiver Class does not define or inherit an implementation of the resolved method 'abstract void invoke ()' of interface GetterFunction.`
MyCode:
public class MyMain {
public static void main(String[] args) throws Throwable {
GetterFunction getterFunction;
final MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodType methodType = MethodType.methodType(String.class, Person.class);
final CallSite site = LambdaMetafactory.metafactory(lookup,
"invoke",
MethodType.methodType(GetterFunction.class),
methodType,
lookup.findVirtual(Person.class, "getName", MethodType.methodType(String.class)),
methodType);
getterFunction = (GetterFunction) site.getTarget().invokeExact();
getterFunction.invoke());
}
void invoke(GetterFunction getterFunction){
long startTime = System.CurrentMilliSeconds();
for(int i= 0 ; i<3 ; i++){
getterFunction.invoke();
}
long endTime = System.CurrentMilliSeconds();
System.out.println("Executed Time of a method :: "+ (endTime - startTime));
}
@FunctionalInterface
interface GetterFunction {
void invoke();
}
static class Person {
String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
}`
Despite your question's title, your issue is not related to benchmarking execution time, but rather how to implement a functional interface dynamically via LambdaMetafactory
. I strongly recommend reading the documentation to properly understand the API.
Everything below discusses the issues you are having with LambdaMetafactory
. There is nothing about writing benchmarking code. I recommend reading How do I write a correct micro-benchmark in Java? for such information. In short, you should use a framework like the Java Microbenchmark Harness (JMH).
The AbstractMethodError
you are seeing is due to passing an incorrect MethodType
argument for the interfaceMethodType
parameter. You are passing:
MethodType.methodType(String.class, Person.class)
But that does not match your GetterFunction::invoke()
method whose return type is void
, not String
, and which as zero parameters, not a single parameter of type Person
. Thus, you have not actually implemented the invoke
method. Technically, you should be passing:
MethodType.methodType(void.class)
However, while that will solve the immediate error, it will not give you what you want. The code has other issues.
Your GetterFunction
interface is clearly meant to be a "getter proxy". When you call invoke
it is supposed to return the value of some object's property. Unfortunately, you defined the return type to be void
, which means it cannot return anything. You need to fix the return type. Presumably you want this interface to be able to work with any property type, so you should make the interface generic and have invoke
return T
.
@FunctionalInterface
public interface GetterFunction<T> {
T invoke();
}
You should also consider whether you want to capture an instance of the object you want to call a getter on, or if you want to give invoke
a parameter so that you can pass any object of a given type.
If you were to implement the above GetterFunction
as a lambda it would look like:
Person person = new Person("John Doe");
GetterFunction<String> getter = () -> person.getName();
That lambda has captured the person
instance. Each call to getter.invoke()
will return the name of that specific person. An upside to this approach is that you do not need to keep an explicit reference to the person in order to call the getter. But a downside is that you can never change which person you invoke the getter on.
You have to represent this capturing when creating a GetterFunction
via LambdaMetafactory
as well. This is done via the factoryType
argument. In this case, the argument would be:
MethodType.methodType(GetterFunction.class, Person.class)
And then you would need to pass a Person
when creating the GetterFunction
:
site.getTarget().invokeExact(somePersonObject)
The other option to capturing the object is to give invoke
a parameter.
@FunctionalInterface
public interface GetterFunction<T, U> {
U invoke(T instance);
}
An upside to this is that you can create a single GetterFunction
for a given property and then use it with any instance of T
. A downside is that you now have to have a direct reference to T
in order to call invoke
.
Note implementing this GetterFunction
as a lambda would look like:
GetterFunction<Person, String> getter = person -> person.getName();
No more capturing of a Person
.
Here is an example that implements a functional interface via LambdaMetafactory
. Note this example does not include any benchmark code.
import static java.lang.invoke.MethodType.methodType;
import java.lang.invoke.LambdaMetafactory;
import java.lang.invoke.MethodHandles;
public class Main {
public record Person(String name) {}
@FunctionalInterface
public interface Getter<T, U> {
U invoke(T instance);
}
public static void main(String[] args) throws Throwable {
// The lookup context.
var caller = MethodHandles.lookup();
// The name of the functional interface's method that we are implementing.
var interfaceMethodName = "invoke";
// The signature of the CallSite. The return type is 'Getter' because that is the
// functional interface we are implementing. If you need to implement "capturing"
// then you would specify parameters for the MethodType as needed.
var factoryType = methodType(Getter.class);
// The signature of the functional interface's method that we are implementing. The
// return type and parameter are both generic in this case. Since the left-most upper
// bound of each type parameter is 'Object', that is the type used. Note you could also
// use 'MethodType.genericMethodType(1)' here.
var interfaceMethodType = methodType(Object.class, Object.class);
// The method we want the functional interface's method to invoke and return the result
// of. In other words, the "getter" we want to invoke. Note that in this case 'Person' is
// a record, so the accessor method's name is just "name", not "getName".
var implementation = caller.findVirtual(Person.class, "name", methodType(String.class));
// The "parameterization" of the *generic* functional interface we are implementing. This
// example is specifically creating a `Getter<Person, String>` to get the name of a given
// 'Person' object.
var dynamicMethodType = methodType(String.class, Person.class);
// Create 'Getter<Person, String>' factory.
var factory =
LambdaMetafactory.metafactory(
caller,
interfaceMethodName,
factoryType,
interfaceMethodType,
implementation,
dynamicMethodType);
// Create an instance of 'Getter<Person, String>'.
var getter = (Getter<Person, String>) factory.getTarget().invoke();
// Test 'getter'.
var person = new Person("John Doe");
System.out.println(getter.invoke(person));
}
}
Ouptut:
John Doe