So I'm creating a library that allows users to pass a Class<?>
and collect all static methods with a specific annotation (and other criteria, such as a certain parameter count and types) and convert them into lambda FunctionalInterfaces
that my library will use internally for processing.
For example:
Say I have the following class tree:
public abstract class AbstractParent {
public String sayHi() {
return getClass().getName() + " instance says hi!";
}
}
with subclasses:
public class ChildOne extends AbstractParent {
public int childOneSpecialMethod() {
return 2558445;
}
}
public class ChildTwo extends AbstractParent {
public int childTwoSpecialMethod() {
return 484848;
}
}
My library allows for users to annotate a class's static methods with the following annotation:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ProcessAnnotation {}
with the following rules: the static method first parameter be an instance of AbstractParent
, and its second parameter must be a String
, and must return a String
. So, something like this:
public class GeneralProcessor {
@ProcessAnnotation
public static String easyProcessing(ChildOne one, String otherArg) {
//Some code
System.out.println(" === In processing for ChildOne types");
return otherArg + one.toString();
}
@ProcessAnnotation
public static String easyProcessing(ChildTwo two, String otherArg) {
//Some code
System.out.println(" === In processing for ChildTwo types");
return otherArg + two.toString();
}
}
On the library-side of things, I want to collect all these methods so that I can use them for some processing while doing it in a relatively fast manner and I found that MethodHandles
and LambdaMetaFactory
is the best way to do this.
Specifically, I want to invoke these collected methods using my own FunctionalInterface
:
@FunctionalInterface
public interface ProcessInterface<T extends AbstractParent> {
public String process(T obj, String extraArg);
}
So far, what I've tried is something like this:
public static List<ProcessInterface<? extends AbstractParent>> generate(Class<?> targetClass) throws Throwable {
ArrayList<ProcessInterface<? extends AbstractParent>> processors = new ArrayList<>();
for (Method method : targetClass.getDeclaredMethods()) {
if (method.isAnnotationPresent(ProcessAnnotation.class) &&
method.getParameterCount() == 2 &&
AbstractParent.class.isAssignableFrom(method.getParameterTypes()[0]) &&
method.getParameterTypes()[1] == String.class) {
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle handle = lookup.unreflect(method);
CallSite callSite = LambdaMetafactory.metafactory(lookup,
"process",
MethodType.methodType(ProcessInterface.class),
MethodType.methodType(String.class,
method.getParameterTypes()[0],
String.class),
handle,
handle.type());
ProcessInterface<? extends AbstractParent> func = (ProcessInterface<? extends AbstractParent>) callSite.getTarget().invoke();
processors.add(func);
}
}
return processors;
}
However, I get the following error when I actually invoke the lambda. For example:
List<ProcessInterface<? extends AbstractParent>> interfaces = generate(GeneralProcessor.class);
ChildOne childOne = new ChildOne();
interfaces.get(0).process(childOne, "");
Is there a fix to do this? Or maybe even a better way to achieve this?
What you are doing is inherently not type safe. How do you ensure that
interfaces.get(0)
can process aChildOne
? What if it takes aChildTwo
instead?This is why you are not allowed to pass
childOne
tointerfaces.get(0).process
.interfaces.get(0)
is aProcessInterface<Something>
, but you don't know whatSomething
is. It could beChildOne
, orChildTwo
. This is what the typeProcessInterface<? extends AbstractParent>
means. See also PECS.If
Something
were actuallyChildTwo
, then passing aChildOne
will not work at all. The implementing method does not know how to deal with that. In fact, you can only safely passnull
toprocess
.So let's suppose the user's of your library are responsible for ensuring that type safety, and that you are sure that this is always safe.
In that case, cast
generate
's return value toList<ProcessorInterface<AbstractParent>>
:(You can change the return type of
generate
to this type, if you really want.)ProcessInterface<AbstractParent>
is a type that you can pass all kinds ofAbstractParent
s to itsprocess
method. In fact, since type safety is gone at this point, it doesn't matter which exact type you cast to, as long as it can take anyAbstractParent
.Now if you happen to pass the wrong type of instance to
process
, aClassCastException
will be thrown.As for your
LambdaMetafactory
, you should pass the method type of the interface method for the fourth argument ofmetafactory
, not the implementing method's method type.