Search code examples
javalambdareflectionfunctional-programming

How to use reflection to find annotated Lambda Functions


I have an application that has many declared Lambdas. I've added an annotation to them so that I can use reflection to find all the functions marked with the annotation. They are all defined as:

@FooFunction("abc")
public static Function<Task, Result> myFunc = task -> {... returns new Result}

At startup, my application uses reflection to find all of the annotated functions and add them to the hashmap.

static HashMap<String, Function<Task, Result>> funcMap = new HashMap<>();
static {
   Reflections reflections = new Reflections("my.package", Scanners.values());
   var annotated = reflections.getFieldsAnnotatedWith(FooFunction.class);
   annotated.forEach(aField -> {
   try {
      var annot = aField.getAnnotation(FooFunction.class);
      var key = annot.value();
      funcMap.put(key, aField.get(null);
   } catch (Exception e) {
      ...;
   }
}

The above code definitely won't work, especially on the put since aField.get(null) returns an Object. If I cast the object to Function<Task,Result>, I get an unchecked cast warning. No matter how I circle around it, I can't get rid of the warning (without using Suppress).

I've tried changing the Function<Foo, Bar> to something more generic like Function<?,?> but that took me down another rabbit hole.

All of the functions are declared as static since they really don't need to belong to a specific class. They are grouped under various classes simply for organizational purposes.

The underlying objective is: the API will receive a list of tasks. There are about 100 different Task types. Each Task has an "id" field which is used to determine which Function should be used to process that Task. It looks something like this:

var results = Arrays.stream(request.getTasks())
   .map(task -> functionMap.getOrDefault(task.getId(), unknownTaskFn).apply(task)
   .toList();

My questions:

  1. Is this an antipattern? If so, is there a better prescribed pattern?
  2. How can I go from an Object to a Function<Task,Result> properly to put it into the map?

Thanks


Solution

  • Casting is inevitable, because Field.get returns Object by design, but it could be done without warnings.

    I would also suggest define a custom interface

    public interface TaskResultFunction extends Function<Task, Result> {
    }
    

    and use it for lambda declarations

    @FooFunction("abc")
    public static TaskResultFunction myFunc = task -> {... returns new Result}
    

    (otherwise we will have to deal with ParameterizedTypeReference, but in this case it is not necessary and overcomplicated)

     Map<String, Function<Task, String>> funcMap = ...
    
     // or more strict
     Map<String, TaskResultFunction> funcMap = ...
    
     //...
    
     if (TaskResultFunction.class.isAssignableFrom(field.getType())) {
         TaskResultFunction fn = (TaskResultFunction) field.get(null);
         taskResultFunctions.put(key, fn);
     }