Well, working with types specified at runtime, i.e. unknown at compile-time, is the definition of Reflection. You can’t do reflective operations without Reflection.
What you can do, is to generate an instance of your functional interface which can instantiate the class without using reflection, i.e. having everything resolved prior to calling the interface method, much like the method reference to a member known at compile time works at runtime:
public static MyFactory getRuntimeClass(Class<? extends MyClass> cls) {
try {
MethodHandles.Lookup l = MethodHandles.lookup();
MethodHandle c = l.findConstructor(cls,
MethodType.methodType(void.class, String.class));
MethodType ft = c.type().changeReturnType(MyClass.class);
return (MyFactory)LambdaMetafactory.metafactory(l, "getInstance",
MethodType.methodType(MyFactory.class), ft, c, ft).getTarget().invokeExact();
}
catch(RuntimeException | Error t) {
throw t;
}
catch(Throwable t) {
throw new IllegalArgumentException(t);
}
}
Of course, generating the instance is a reflective operation with all of its disadvantages, like detecting errors at runtime only. Since there is no safety of compile-time checks, there wouldn’t be any generic type safety, if you try this with a generic functional interface.
If you change the interface and forget to adapt this code, it might happen, that you even don’t spot the mistake when executing this getRuntimeClass
method, but only when you actually try to invoke the interface method on the generated instance.
Unlike real method references, this doesn’t bear any caching. That’s ok, if you only map some class names to MyFactory
instances at initialization time and keep the MyFactory
instances. But if you find yourself having to map class names to MyFactory
instances repeatedly, perhaps with the same names, you might want to remember them. The easiest solution is:
static ClassValue<MyFactory> MY_FACTORIES = new ClassValue<MyFactory>() {
protected MyFactory computeValue(Class<?> type) {
return getRuntimeClass(type.asSubclass(MyClass.class));
}
};
which can be used like
MyClass mySubClass = MY_FACTORIES.get(Class.forName(args[0])).getInstance("test");