Search code examples
javareflection

Create extended class via Reflection


I have a nice and clean interface like so:

public interface Mapping<I extends Input, O extends Output> {
    O calculate(I input);

    Class<I> getInputClass();

    Class<O> getOutputClass();
}

Let's assume for simplicity that Input and Output are empty interfaces.

I want to define Mappings at runtime and instantiate them via reflection. One example for such a Mapping is the following:

public class DoublerMapping implements Mapping<DoublerInput, DoublerOutput> {
    @Override
    DoublerOutput calculate(DoublerInput input){
       DoublerOutput output = new DoublerOutput();
       output.setValue(input.getValue() * 2);
       return output;
    }

    @Override
    Class<DoublerInput> getInputClass(){
       return DoublerInput.class;
    }

    @Override
    Class<O> getOutputClass(){
       return DoublerInput.class;
    }
}

When instantiating Mappings by reflection, however, I can only use getInputClass() and getOutputClass() to instantiate Inputs and Outputs respectively (utilizing mapping.getInputClass().getDeclaredConstructor().newInstance()), instead of their actual extension. I don't want the Mapping to take a generic Input as I want to make sure it's getting the proper class I it's expecting. I also can't pull the getValue() and setValue() methods to the interface, as they could take any arguments and have different signatures.

How do I go to create an I extends Input at runtime and feeding it back to the parameterized Mapping class? Not even mapping.execute(mapping.getInputClass().getDeclaredConstructor().newInstance()) compiles.

Or is there a better way to achieve this?


Solution

  • I managed to solve my own issue. The primary problem is to feed the I back into the Mapping class. @rzwitserloot's answer is a good improvement to the avoid verbose and bad code, but still doesn't get the code to compile when calling the calculate method with an Input generated from the Mapping itself.

    I could fin the solution here: https://angelikalanger.com/GenericsFAQ/FAQSections/ProgrammingIdioms.html#FAQ207

    The Mapping interface still needs a method to get the actual I extends Input at runtime like so:

    public interface Mapping<I extends Input, O extends Output> {
      O calculate(I input);
    
      Supplier<I> newInput(); // thanks rzwitserloot!
    
      Class<I> getInputClass(); // needed to make things work
    }
    

    The caller of the generic calculate method has to do the following:

       Output calculateInMapping(Mapping<? extends Input, ? extends Output> mapping) {
          Input input = mapping.newInput().get();
    
          // maybe do some stuff with the input
    
          return calculate(mapping, input);
       }
       
       private static <T extends Input> Output calculate(Mapping<T, ? extends Output> mapping, Input input) {
           return mapping.calculate(mapping.getInputType().cast(input));
       } 
    

    And for completeness, here's how the DoublingMapping would look like:

    public class DoublerMapping implements Mapping<DoublerInput, DoublerOutput> {
      @Override
      DoublerOutput calculate(DoublerInput input){
       DoublerOutput output = new DoublerOutput();
       output.setValue(input.getValue() * 2);
       return output;
      }
    
      @Override
      Supplier<DoublerInput> newInput(){
       return DoublerInput::new;
      }
    
      @Override
      Class<DoublerInput> getInputClass(){
       return DoublerInput.class;
      }
    }
    

    P.S.: If you're willing to tolerate an "unchecked" cast warning, you can also get away without the getInputClass() method above and just cast:

    private static <T extends Input> Output execute(Mapping<T, ? extends Output> mapping, Input input) {
        return mapping.execute((T) input);
    }