[reflect] Use LambdaMetafactory + MethodHandles to (hopefully) speed up generated lambdas

This commit is contained in:
Johannes Frohnmeyer 2022-09-26 15:09:20 +02:00
parent fdfcb51123
commit ea06c7009d
Signed by: Johannes
GPG Key ID: E76429612C2929F4
4 changed files with 466 additions and 19 deletions

View File

@ -4,39 +4,264 @@ import io.gitlab.jfronny.commons.throwable.ThrowingBiFunction;
import io.gitlab.jfronny.commons.throwable.ThrowingFunction;
import io.gitlab.jfronny.commons.throwable.ThrowingSupplier;
import java.lang.reflect.Constructor;
import java.lang.invoke.*;
import java.util.function.*;
@SuppressWarnings("unchecked")
public class Reflect {
// Constructor without parameters
public static <TOut> ThrowingSupplier<TOut, ReflectiveOperationException> getConstructor(Class<?> toConstruct) throws NoSuchMethodException {
Constructor<TOut> constructor = (Constructor<TOut>) toConstruct.getDeclaredConstructor();
constructor.setAccessible(true);
return constructor::newInstance;
private static final MethodHandles.Lookup LOOKUP_ROOT = MethodHandles.lookup();
@Deprecated
public static <TOut> ThrowingSupplier<TOut, ReflectiveOperationException> getConstructor(Class<?> toConstruct) throws Throwable {
Supplier<TOut> constructor = constructor(toConstruct);
return constructor::get;
}
public static <TOut> ThrowingSupplier<TOut, ReflectiveOperationException> getConstructor(String targetClassName) throws NoSuchMethodException, ClassNotFoundException {
@Deprecated
public static <TOut> ThrowingSupplier<TOut, ReflectiveOperationException> getConstructor(String targetClassName) throws Throwable {
return getConstructor(Class.forName(targetClassName));
}
// Constructor with one parameter
public static <TIn, TOut> ThrowingFunction<TIn, TOut, ReflectiveOperationException> getConstructor(Class<?> toConstruct, Class<TIn> parameterType) throws NoSuchMethodException {
Constructor<TOut> constructor = (Constructor<TOut>) toConstruct.getDeclaredConstructor(parameterType);
constructor.setAccessible(true);
return constructor::newInstance;
@Deprecated
public static <TIn, TOut> ThrowingFunction<TIn, TOut, ReflectiveOperationException> getConstructor(Class<?> toConstruct, Class<TIn> parameterType) throws Throwable {
Function<TIn, TOut> constructor = constructor(toConstruct, parameterType);
return constructor::apply;
}
public static <TIn, TOut> ThrowingFunction<TIn, TOut, ReflectiveOperationException> getConstructor(String targetClassName, Class<TIn> parameterType) throws NoSuchMethodException, ClassNotFoundException {
@Deprecated
public static <TIn, TOut> ThrowingFunction<TIn, TOut, ReflectiveOperationException> getConstructor(String targetClassName, Class<TIn> parameterType) throws Throwable {
return getConstructor(Class.forName(targetClassName), parameterType);
}
// Constructor with two parameters
public static <TIn1, TIn2, TOut> ThrowingBiFunction<TIn1, TIn2, TOut, ReflectiveOperationException> getConstructor(Class<?> toConstruct, Class<TIn1> parameterType1, Class<TIn2> parameterType2) throws NoSuchMethodException {
Constructor<TOut> constructor = (Constructor<TOut>) toConstruct.getDeclaredConstructor(parameterType1, parameterType2);
constructor.setAccessible(true);
return constructor::newInstance;
@Deprecated
public static <TIn1, TIn2, TOut> ThrowingBiFunction<TIn1, TIn2, TOut, ReflectiveOperationException> getConstructor(Class<?> toConstruct, Class<TIn1> parameterType1, Class<TIn2> parameterType2) throws Throwable {
BiFunction<TIn1, TIn2, TOut> constructor = constructor(toConstruct, parameterType1, parameterType2);
return constructor::apply;
}
public static <TIn1, TIn2, TOut> ThrowingBiFunction<TIn1, TIn2, TOut, ReflectiveOperationException> getConstructor(String targetClassName, Class<TIn1> parameterType1, Class<TIn2> parameterType2) throws NoSuchMethodException, ClassNotFoundException {
@Deprecated
public static <TIn1, TIn2, TOut> ThrowingBiFunction<TIn1, TIn2, TOut, ReflectiveOperationException> getConstructor(String targetClassName, Class<TIn1> parameterType1, Class<TIn2> parameterType2) throws Throwable {
return getConstructor(Class.forName(targetClassName), parameterType1, parameterType2);
}
// Constructor without parameters
public static <TOut> Supplier<TOut> constructor(Class<?> toConstruct) throws Throwable {
MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(toConstruct, LOOKUP_ROOT);
return (Supplier<TOut>) LambdaMetafactory.metafactory(
lookup,
"get",
MethodType.methodType(Supplier.class),
MethodType.methodType(Object.class),
lookup.unreflectConstructor(toConstruct.getDeclaredConstructor()),
MethodType.methodType(toConstruct)
).getTarget().invoke();
}
public static <TOut> Supplier<TOut> constructor(String targetClassName) throws Throwable {
return constructor(Class.forName(targetClassName));
}
// Constructor with one parameter
public static <TIn, TOut> Function<TIn, TOut> constructor(Class<?> toConstruct, Class<TIn> parameterType) throws Throwable {
MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(toConstruct, LOOKUP_ROOT);
return (Function<TIn, TOut>) LambdaMetafactory.metafactory(
lookup,
"apply",
MethodType.methodType(Function.class),
MethodType.methodType(Object.class, Object.class),
lookup.unreflectConstructor(toConstruct.getDeclaredConstructor(parameterType)),
MethodType.methodType(toConstruct, parameterType)
).getTarget().invoke();
}
public static <TIn, TOut> Function<TIn, TOut> constructor(String targetClassName, Class<TIn> parameterType) throws Throwable {
return constructor(Class.forName(targetClassName), parameterType);
}
// Constructor with two parameters
public static <TIn1, TIn2, TOut> BiFunction<TIn1, TIn2, TOut> constructor(Class<?> toConstruct, Class<TIn1> parameterType1, Class<TIn2> parameterType2) throws Throwable {
MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(toConstruct, LOOKUP_ROOT);
return (BiFunction<TIn1, TIn2, TOut>) LambdaMetafactory.metafactory(
lookup,
"apply",
MethodType.methodType(BiFunction.class),
MethodType.methodType(Object.class, Object.class, Object.class),
lookup.unreflectConstructor(toConstruct.getDeclaredConstructor(parameterType1, parameterType2)),
MethodType.methodType(toConstruct, parameterType1, parameterType2)
).getTarget().invoke();
}
public static <TIn1, TIn2, TOut> BiFunction<TIn1, TIn2, TOut> constructor(String targetClassName, Class<TIn1> parameterType1, Class<TIn2> parameterType2) throws Throwable {
return constructor(Class.forName(targetClassName), parameterType1, parameterType2);
}
// Procedure without parameters
public static Runnable staticProcedure(Class<?> targetClass, String name) throws Throwable {
MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(targetClass, LOOKUP_ROOT);
return (Runnable) LambdaMetafactory.metafactory(
lookup,
"run",
MethodType.methodType(Runnable.class),
MethodType.methodType(Void.TYPE),
lookup.unreflect(targetClass.getDeclaredMethod(name)),
MethodType.methodType(Void.TYPE)
).getTarget().invoke();
}
public static Runnable staticProcedure(String targetClassName, String name) throws Throwable {
return staticProcedure(Class.forName(targetClassName), name);
}
// Procedure with one parameter
public static <TIn> Consumer<TIn> staticProcedure(Class<?> targetClass, String name, Class<TIn> parameterType) throws Throwable {
MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(targetClass, LOOKUP_ROOT);
return (Consumer<TIn>) LambdaMetafactory.metafactory(
lookup,
"accept",
MethodType.methodType(Consumer.class),
MethodType.methodType(Void.TYPE, Object.class),
lookup.unreflect(targetClass.getDeclaredMethod(name, parameterType)),
MethodType.methodType(Void.TYPE, parameterType)
).getTarget().invoke();
}
public static <TIn> Consumer<TIn> staticProcedure(String targetClassName, String name, Class<TIn> parameterType) throws Throwable {
return staticProcedure(Class.forName(targetClassName), name, parameterType);
}
// Procedure with two parameters
public static <TIn1, TIn2> BiConsumer<TIn1, TIn2> staticProcedure(Class<?> targetClass, String name, Class<TIn1> parameterType1, Class<TIn2> parameterType2) throws Throwable {
MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(targetClass, LOOKUP_ROOT);
return (BiConsumer<TIn1, TIn2>) LambdaMetafactory.metafactory(
lookup,
"accept",
MethodType.methodType(BiConsumer.class),
MethodType.methodType(Void.TYPE, Object.class, Object.class),
lookup.unreflect(targetClass.getDeclaredMethod(name, parameterType1, parameterType2)),
MethodType.methodType(Void.TYPE, parameterType1, parameterType2)
).getTarget().invoke();
}
public static <TIn1, TIn2> BiConsumer<TIn1, TIn2> staticProcedure(String targetClassName, String name, Class<TIn1> parameterType1, Class<TIn2> parameterType2) throws Throwable {
return staticProcedure(Class.forName(targetClassName), name, parameterType1, parameterType2);
}
// Function without parameters
public static <TOut> Supplier<TOut> staticFunction(Class<?> targetClass, String name, Class<TOut> returnType) throws Throwable {
MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(targetClass, LOOKUP_ROOT);
return (Supplier<TOut>) LambdaMetafactory.metafactory(
lookup,
"get",
MethodType.methodType(Supplier.class),
MethodType.methodType(Object.class),
lookup.unreflect(targetClass.getDeclaredMethod(name)),
MethodType.methodType(returnType)
).getTarget().invoke();
}
public static <TOut> Supplier<TOut> staticFunction(String targetClassName, String name, Class<TOut> returnType) throws Throwable {
return staticFunction(Class.forName(targetClassName), name, returnType);
}
// Function with one parameter
public static <TIn, TOut> Function<TIn, TOut> staticFunction(Class<?> targetClass, String name, Class<TOut> returnType, Class<TIn> parameterType) throws Throwable {
MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(targetClass, LOOKUP_ROOT);
return (Function<TIn, TOut>) LambdaMetafactory.metafactory(
lookup,
"apply",
MethodType.methodType(Function.class),
MethodType.methodType(Object.class, Object.class),
lookup.unreflect(targetClass.getDeclaredMethod(name, parameterType)),
MethodType.methodType(returnType, parameterType)
).getTarget().invoke();
}
public static <TIn, TOut> Function<TIn, TOut> staticFunction(String targetClassName, String name, Class<TOut> returnType, Class<TIn> parameterType) throws Throwable {
return staticFunction(Class.forName(targetClassName), name, returnType, parameterType);
}
// Function with two parameters
public static <TIn1, TIn2, TOut> BiFunction<TIn1, TIn2, TOut> staticFunction(Class<?> targetClass, String name, Class<TOut> returnType, Class<TIn1> parameterType1, Class<TIn2> parameterType2) throws Throwable {
MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(targetClass, LOOKUP_ROOT);
return (BiFunction<TIn1, TIn2, TOut>) LambdaMetafactory.metafactory(
lookup,
"apply",
MethodType.methodType(BiFunction.class),
MethodType.methodType(Object.class, Object.class, Object.class),
lookup.unreflect(targetClass.getDeclaredMethod(name, parameterType1, parameterType2)),
MethodType.methodType(returnType, parameterType1, parameterType2)
).getTarget().invoke();
}
public static <TIn1, TIn2, TOut> BiFunction<TIn1, TIn2, TOut> staticFunction(String targetClassName, String name, Class<TOut> returnType, Class<TIn1> parameterType1, Class<TIn2> parameterType2) throws Throwable {
return staticFunction(Class.forName(targetClassName), name, returnType, parameterType1, parameterType2);
}
// Procedure without parameters
public static <TTarget> Consumer<TTarget> instanceProcedure(Class<TTarget> targetClass, String name) throws Throwable {
MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(targetClass, LOOKUP_ROOT);
return (Consumer<TTarget>) LambdaMetafactory.metafactory(
lookup,
"accept",
MethodType.methodType(Consumer.class),
MethodType.methodType(Void.TYPE, Object.class),
lookup.unreflect(targetClass.getDeclaredMethod(name)),
MethodType.methodType(Void.TYPE, targetClass)
).getTarget().invoke();
}
public static <TTarget> Consumer<TTarget> instanceProcedure(String targetClassName, String name) throws Throwable {
return (Consumer<TTarget>) instanceProcedure(Class.forName(targetClassName), name);
}
// Procedure with one parameter
public static <TTarget, TIn> BiConsumer<TTarget, TIn> instanceProcedure(Class<TTarget> targetClass, String name, Class<TIn> parameterType) throws Throwable {
MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(targetClass, LOOKUP_ROOT);
return (BiConsumer<TTarget, TIn>) LambdaMetafactory.metafactory(
lookup,
"accept",
MethodType.methodType(BiConsumer.class),
MethodType.methodType(Void.TYPE, Object.class, Object.class),
lookup.unreflect(targetClass.getDeclaredMethod(name, parameterType)),
MethodType.methodType(Void.TYPE, targetClass, parameterType)
).getTarget().invoke();
}
public static <TTarget, TIn> BiConsumer<TTarget, TIn> instanceProcedure(String targetClassName, String name, Class<TIn> parameterType) throws Throwable {
return (BiConsumer<TTarget, TIn>) instanceProcedure(Class.forName(targetClassName), name, parameterType);
}
// Function without parameters
public static <TTarget, TOut> Function<TTarget, TOut> instanceFunction(Class<TTarget> targetClass, String name, Class<TOut> returnType) throws Throwable {
MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(targetClass, LOOKUP_ROOT);
return (Function<TTarget, TOut>) LambdaMetafactory.metafactory(
lookup,
"apply",
MethodType.methodType(Function.class),
MethodType.methodType(Object.class, Object.class),
lookup.unreflect(targetClass.getDeclaredMethod(name)),
MethodType.methodType(returnType, targetClass)
).getTarget().invoke();
}
public static <TTarget, TOut> Function<TTarget, TOut> instanceFunction(String targetClassName, String name, Class<TOut> returnType) throws Throwable {
return (Function<TTarget, TOut>) instanceFunction(Class.forName(targetClassName), name, returnType);
}
// Function with one parameter
public static <TTarget, TIn, TOut> BiFunction<TTarget, TIn, TOut> instanceFunction(Class<TTarget> targetClass, String name, Class<TOut> returnType, Class<TIn> parameterType) throws Throwable {
MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(targetClass, LOOKUP_ROOT);
return (BiFunction<TTarget, TIn, TOut>) LambdaMetafactory.metafactory(
lookup,
"apply",
MethodType.methodType(BiFunction.class),
MethodType.methodType(Object.class, Object.class, Object.class),
lookup.unreflect(targetClass.getDeclaredMethod(name, parameterType)),
MethodType.methodType(returnType, targetClass, parameterType)
).getTarget().invoke();
}
public static <TTarget, TIn, TOut> BiFunction<TTarget, TIn, TOut> instanceFunction(String targetClassName, String name, Class<TOut> returnType, Class<TIn> parameterType) throws Throwable {
return (BiFunction<TTarget, TIn, TOut>) instanceFunction(Class.forName(targetClassName), name, returnType, parameterType);
}
}

View File

@ -0,0 +1,97 @@
package io.gitlab.jfronny.commons.test;
import io.gitlab.jfronny.commons.reflect.Reflect;
import org.junit.jupiter.api.Test;
import some.other.location.InstanceConstruct;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
public class ReflectTest {
private static final String CLASS_NAME = "some.other.location.ToConstruct";
@Test
void noArgConstructor() {
assertEquals(InstanceConstruct.construct(), assertDoesNotThrow(() -> Reflect.constructor(CLASS_NAME).get()));
assertEquals(InstanceConstruct.LastProc.C0, InstanceConstruct.getLastProc());
}
@Test
void oneArgConstructor() {
String arg1 = "Some arg";
assertEquals(InstanceConstruct.construct(arg1), assertDoesNotThrow(() -> Reflect.constructor(CLASS_NAME, String.class).apply(arg1)));
assertEquals(InstanceConstruct.LastProc.C1, InstanceConstruct.getLastProc());
}
@Test
void twoArgConstructor() {
String arg1 = "Some other arg";
List<String> arg2 = List.of("Hello", "World");
assertEquals(InstanceConstruct.construct(arg1, arg2), assertDoesNotThrow(() -> Reflect.constructor(CLASS_NAME, String.class, List.class).apply(arg1, arg2)));
assertEquals(InstanceConstruct.LastProc.C2, InstanceConstruct.getLastProc());
}
@Test
void noArgProcedure() {
assertDoesNotThrow(() -> Reflect.staticProcedure(CLASS_NAME, "procedure").run());
assertEquals(InstanceConstruct.LastProc.P0, InstanceConstruct.getLastProc());
}
@Test
void oneArgProcedure() {
String arg1 = "Some arg";
assertDoesNotThrow(() -> Reflect.staticProcedure(CLASS_NAME, "procedure", String.class).accept(arg1));
assertEquals(InstanceConstruct.LastProc.P1, InstanceConstruct.getLastProc());
}
@Test
void noArgInstanceProcedure() {
assertDoesNotThrow(() -> Reflect.instanceProcedure(CLASS_NAME, "instanceProcedure").accept(InstanceConstruct.construct()));
assertEquals(InstanceConstruct.LastProc.P1, InstanceConstruct.getLastProc());
}
@Test
void twoArgProcedure() {
String arg1 = "Some other arg";
List<String> arg2 = List.of("Hello", "World");
assertDoesNotThrow(() -> Reflect.staticProcedure(CLASS_NAME, "procedure", String.class, List.class).accept(arg1, arg2));
assertEquals(InstanceConstruct.LastProc.P2, InstanceConstruct.getLastProc());
}
@Test
void oneArgInstanceProcedure() {
String arg1 = "Some arg";
assertDoesNotThrow(() -> Reflect.instanceProcedure(CLASS_NAME, "instanceProcedure", String.class).accept(InstanceConstruct.construct(), arg1));
assertEquals(InstanceConstruct.LastProc.P2, InstanceConstruct.getLastProc());
}
@Test
void noArgFunction() { //TODO fix
assertEquals(assertDoesNotThrow(() -> Reflect.staticFunction(CLASS_NAME, "function", String.class).get()), InstanceConstruct.getLastProc().toString());
}
@Test
void oneArgFunction() {
String arg1 = "Some arg";
assertEquals(assertDoesNotThrow(() -> Reflect.staticFunction(CLASS_NAME, "function", String.class, String.class).apply(arg1)), InstanceConstruct.getLastProc().toString());
}
@Test
void noArgInstanceFunction() {
assertEquals(assertDoesNotThrow(() -> Reflect.instanceFunction(CLASS_NAME, "instanceFunction", String.class).apply(InstanceConstruct.construct())), InstanceConstruct.getLastProc().toString());
}
@Test
void twoArgFunction() {
String arg1 = "Some other arg";
List<String> arg2 = List.of("Hello", "World");
assertEquals(assertDoesNotThrow(() -> Reflect.staticFunction(CLASS_NAME, "function", String.class, String.class, List.class).apply(arg1, arg2)), InstanceConstruct.getLastProc().toString());
}
@Test
void oneArgInstanceFunction() {
String arg1 = "Some arg";
assertEquals(assertDoesNotThrow(() -> Reflect.instanceFunction(CLASS_NAME, "instanceFunction", String.class, String.class).apply(InstanceConstruct.construct(), arg1)), InstanceConstruct.getLastProc().toString());
}
}

View File

@ -0,0 +1,29 @@
package some.other.location;
import java.util.List;
public class InstanceConstruct {
static LastProc lastProc;
public static LastProc getLastProc() {
LastProc tmp = lastProc;
lastProc = null;
return tmp;
}
public static Object construct() {
return ToConstruct.create();
}
public static Object construct(String arg1) {
return ToConstruct.create(arg1);
}
public static Object construct(String arg1, List<String> arg2) {
return ToConstruct.create(arg1, arg2);
}
public enum LastProc {
C0, C1, C2, P0, P1, P2, F0, F1, F2
}
}

View File

@ -0,0 +1,96 @@
package some.other.location;
import java.util.List;
import java.util.Objects;
class ToConstruct {
private final int argCount;
private final String arg1;
private final List<String> arg2;
private ToConstruct() {
InstanceConstruct.lastProc = InstanceConstruct.LastProc.C0;
this.argCount = 1;
this.arg1 = null;
this.arg2 = null;
}
private ToConstruct(String arg1) {
InstanceConstruct.lastProc = InstanceConstruct.LastProc.C1;
this.argCount = 2;
this.arg1 = arg1;
this.arg2 = null;
}
private ToConstruct(String arg1, List<String> arg2) {
InstanceConstruct.lastProc = InstanceConstruct.LastProc.C2;
this.argCount = 3;
this.arg1 = arg1;
this.arg2 = arg2;
}
static Object create() {
return new ToConstruct();
}
static Object create(String arg1) {
return new ToConstruct(arg1);
}
static Object create(String arg1, List<String> arg2) {
return new ToConstruct(arg1, arg2);
}
@Override
public boolean equals(Object obj) {
return obj instanceof ToConstruct other
&& argCount == other.argCount
&& Objects.equals(arg1, other.arg1)
&& Objects.equals(arg2, other.arg2);
}
private static void procedure() {
InstanceConstruct.lastProc = InstanceConstruct.LastProc.P0;
}
private void instanceProcedure() {
InstanceConstruct.lastProc = InstanceConstruct.LastProc.P1;
}
private static void procedure(String arg1) {
InstanceConstruct.lastProc = InstanceConstruct.LastProc.P1;
}
private void instanceProcedure(String arg1) {
InstanceConstruct.lastProc = InstanceConstruct.LastProc.P2;
}
private static void procedure(String arg1, List<String> arg2) {
InstanceConstruct.lastProc = InstanceConstruct.LastProc.P2;
}
private static String function() {
InstanceConstruct.lastProc = InstanceConstruct.LastProc.F0;
return InstanceConstruct.lastProc.toString();
}
private String instanceFunction() {
InstanceConstruct.lastProc = InstanceConstruct.LastProc.F1;
return InstanceConstruct.lastProc.toString();
}
private static String function(String arg1) {
InstanceConstruct.lastProc = InstanceConstruct.LastProc.F1;
return InstanceConstruct.lastProc.toString();
}
private String instanceFunction(String arg1) {
InstanceConstruct.lastProc = InstanceConstruct.LastProc.F2;
return InstanceConstruct.lastProc.toString();
}
private static String function(String arg1, List<String> arg2) {
InstanceConstruct.lastProc = InstanceConstruct.LastProc.F2;
return InstanceConstruct.lastProc.toString();
}
}