LanternPowered/Lmbda

Repository files navigation

This is library that can be used to generate lambdas from method handles with any kind of access modifier without any performance loss. This includes methods, constructors, field accessors (getter, setter) and any other MethodHandle that can be constructed.

Note: Since Java 23, MethodHandleProxies.asInterfaceInstance(...) has been re-written and performance is now in line with direct access and lmbda implementations. It might be worth looking into that method instead to avoid adding new dependencies. However, not all features like abstract functional classes will be supported.

Every available MethodHandle can be implemented by a functional interface. But only if the two method signatures match. Object types will be auto casted to the target type if possible, the same goes for boxing/unboxing of primitive types.

Non-static methods will always take an extra parameter which represents the target object of the method. This is always the first parameter.

Note: Generating functions requires that you have access to the target class. Since Java 9, this is even more important because the module your code is located in must be able to access the module of the target class, see MethodHandles#privateLookupIn for more info.

Consider the following class which holds a static setter function.

class MyObject {
    private static void set(int value) {
        // ...
    }
}

If you want to generate a function for the static set(int) method, you need to use a interface which takes one parameter (value) and return void to reflect the method signature. The IntConsumer is a good choice available within the Java 8 API.

final MethodHandles.Lookup lookup = MethodHandlesExtensions.privateLookupIn(MyObject.class, MethodHandles.lookup());
final MethodHandle methodHandle = lookup.findStatic(MyObject.class, "set", MethodType.methodType(void.class, int.class));

final IntConsumer setter = LambdaFactory.create(LambdaType.of(IntConsumer.class), methodHandle);
setter.accept(1000);

Consider the following class which holds a setter function.

class MyObject {
    private void set(int value) {
        // ...
    }
}

If you want to generate a function for the static set(int) method, you need to use a interface which takes two parameters:

  1. The target of where the method is declared, this will be the target object on which the method is invoked. E.g. target.set(value)
  2. The value that will be passed to the set method.

the first one is the target where the method is declared, MyObject (value) and return void to reflect the method signature. The ObjIntConsumer is a good choice available within the Java 8 API.

final MethodHandles.Lookup lookup = MethodHandlesExtensions.privateLookupIn(MyObject.class, MethodHandles.lookup());
final MethodHandle methodHandle = lookup.findVirtual(MyObject.class, "set", MethodType.methodType(void.class, int.class));

final ObjIntConsumer<MyObject> setter = LambdaFactory.create(new LambdaType<ObjIntConsumer<MyObject>>() {}, methodHandle);

final MyObject myObject = new MyObject();
setter.accept(myObject, 1000);

Below you can find a few benchmarks. lmbda will be generated at runtime, all the other ones are implemented manually. The generated structure of the lmbda is almost the same as the static_mh.

The following benchmark implements a lambda function to access a int field, a ToIntFunction is implemented in this case to avoid boxing/unboxing.

The benchmark was run on an AMD Ryzen 9 5900X CPU and using JDK 16.0.2

BenchmarkModeCntScoreErrorUnits
IntGetterFieldBenchmark.directavgt151.304± 0.008ns/op
IntGetterFieldBenchmark.fieldConstavgt152.698± 0.061ns/op
IntGetterFieldBenchmark.fieldDynavgt152.986± 0.005ns/op
IntGetterFieldBenchmark.lmbdaavgt151.299± 0.005ns/op
IntGetterFieldBenchmark.methodHandleConstavgt151.300± 0.004ns/op
IntGetterFieldBenchmark.methodHandleDynavgt153.013± 0.018ns/op
IntGetterFieldBenchmark.methodHandleProxyavgt157.530± 0.185ns/op
IntGetterFieldBenchmark.plainavgt151.304± 0.006ns/op

This benchmark returns a Integer through the getValue method from a target object.

BenchmarkModeCntScoreErrorUnits
IntGetterMethodBenchmark.directavgt151.372± 0.003ns/op
IntGetterMethodBenchmark.lambdaMetafactoryavgt151.387± 0.024ns/op
IntGetterMethodBenchmark.lmbdaavgt151.370± 0.004ns/op
IntGetterMethodBenchmark.methodConstavgt153.731± 0.106ns/op
IntGetterMethodBenchmark.methodDynavgt154.164± 0.139ns/op
IntGetterMethodBenchmark.methodHandleConstavgt151.612± 0.379ns/op
IntGetterMethodBenchmark.methodHandleDynavgt153.852± 0.116ns/op
IntGetterMethodBenchmark.methodHandleProxyavgt158.085± 0.070ns/op
IntGetterMethodBenchmark.plainavgt151.398± 0.025ns/op