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 Mapping
s 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?
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);
}