Speed up your Java reflection with LambdaMetafactory and Aide

Danila Rassokhin
4 min readFeb 4, 2023

--

Java 8 brought a lot of cool things such as lambda expressions. Here I will show you how to invoke methods fast reflectively using LambdaMetafactory and more generic Aide library.

Not everyone knows, that lambdas can be used to call methods reflectively. Why this way? Cause such calls will be performed as fast as direct method calls (they will be transformed to direct calls, actually). If you worked with reflection, you should know that it is very slow.

I will show you how to use raw LambdaMetafactory to call methods fast and how it can be more comfortable with Aide library.

I won’t tell a lot about lambdas and invokedynamic , you can read about it on oracle blog.

So, we can wrap our reflected method into dynamic lambda expression with LambdaMetafactory.metafactory():

private static Function createGetter(final MethodHandles.Lookup lookup,
final MethodHandle getter) throws Exception{
final CallSite site = LambdaMetafactory.metafactory(lookup, // MethodHandles.Lookup
"apply", // Name of lambda method from interface
// (Function.class) in our case
MethodType.methodType(Function.class), // MethodType of interface
MethodType.methodType(Object.class, Object.class), //Signature of method Function.apply after type erasure
getter, // MethodHandle
getter.type()); //Actual signature of getter
try {
return (Function) site.getTarget().invokeExact();
} catch (final Exception e) {
throw e;
} catch (final Throwable e) {
throw new Error(e);
}
}

This code seems to be fine, but it is applicable only for one method type — getter. Of course we can make something like this:

private static <F> F createWrapper(final MethodHandles.Lookup lookup,
final MethodHandle original,
final String lambdaName,
final Class<F> interfaceType,
final Method wrapperMethod) throws Exception{
final CallSite site = LambdaMetafactory.metafactory(lookup, // MethodHandles.Lookup
lambdaName, // Name of lambda method from interface
MethodType.methodType(interfaceType), // MethodType of interface
MethodType.methodType(wrapperMethod.getReturnType(), wrapperMethod.getParameterTypes()), //Signature of wrapper lambda
original, // MethodHandle
original.type()); //Actual signature of method
try {
return (F) site.getTarget().invoke();
} catch (final Exception e) {
throw e;
} catch (final Throwable e) {
throw new Error(e);
}
}

But in this case we need to know what interface used to wrap our method. It is worth noting that you still need to know interface method to call next in your code. E.g. you can’t just call something generic like wrapper.invoke() , you need to know exact method signature in which you wrapped your method.

In this case you can use Aide library and it’s module called reflection . It can easily create wrappers with provided lambdas without boilerplate code or even choose wrapper and invoke it instead of you. I won’t show you all of functionality, you can read it on Aide Wiki. I will show only simple wrapping with auto wrapper choice and invocation.

Firsly we need to add dependency in pom.xml:

<dependency>
<groupId>tech.hiddenproject</groupId>
<artifactId>aide-reflection</artifactId>
<version>1.2</version>
</dependency>

Or for gradle:

implementation 'tech:hiddenproject:aide-reflection:1.2'

So, let’s assume we have some class:

 public class TestClass {

public static int staticMethod(String s) {
System.out.println("Static invoked");
return s.length();
}

}

We want to reflectively call our staticMethod() . With Aide we can do such a simple thing:

// Get LambdaWrapperHolder isntance with default LambdaWrapper interface loaded
LambdaWrapperHolder lambdaWrapperHolder = LambdaWrapperHolder.DEFAULT;
// Find static method
Method staticMethod = ReflectionUtil.getMethod(TestClass.class, "staticMethod", String.class);
// Wrap static method
// LambdaWrapper - default wrapper interface from Aide
// Void - caller class, in case of static method we don't need caller, so Void
// Integer - return type
MethodHolder<LambdaWrapper, Void, Integer> staticHolder = lambdaWrapperHolder.wrapSafe(staticMethod);
// Invoke static method without caller
int staticResult = staticHolder.invokeStatic("Hello");

System.out.println("Static result: " + staticResult);

That’s all! If we run this code we’ll get:

Static invoked
Static result: 5

We can also call constructors:

public class TestClass {

public TestClass() {
System.out.println("Test class constructor invoked!");
}

}
// Find constructor of TestClass
Constructor<TestClass> constructorMethod = ReflectionUtil.getConstructor(TestClass.class);
// Wrap constructor
// We use Void as caller class, cause we don't need any caller for constructor
MethodHolder<LambdaWrapper, Void, TestClass> constructor = lambdaWrapperHolder.wrapSafe(constructorMethod);
// Call constructor
// We use invokeStatic(), cause constructor don't need caller object
TestClass instance = constructor.invokeStatic();

Aide has no any dirty hacks it just provides you predefined wrappers and a thing called ArgumentMatcher to know which wrapper will be called and how. Default LambdaWrapper interface has a lot of predefined lambdas which will be enough for most of methods:

  • Getters
  • Setters
  • Consumers (0–6 args)
  • Functions (0–6 args)
  • Action (void action())
  • Construct(T construct())

Aide also allows you to load your own wrappers into it and create argument matchers with simple API.

Aide can be used in other frameworks, libraries development to speed up and simplify methods calls from other classes.

It is really important, that Aide uses LambdaMetafactory internally, so you can call only accessible (public) methods and constructors with it. For not accessible methods you can only use standard reflection with Method or MethodHandle. Calls to not accessible methods is impossible, cause LambdaMetafactory wrapping your methods into simple anonymous methods, so in result they become direct methods calls and not reflective.

You can learn more about Aide on it’s GitHub: https://github.com/CrissNamon/aide

--

--