Reputation: 379
Having the following classes:
public interface Step<C extends Config> {
void setConfig(C config);
}
and
public class ValidationStep implements Step<ValidationConf> {
public void setConfig(ValidationConf conf) {}
// implementation
}
and
public class ProcessStep implements Step<ProcessConf> {
public void setConfig(ProcessConf conf) {}
// implementation
}
and
public interface Config {
Class<? extends Step> type();
}
and
public class ValidationConf implements Config {
public Class<? extends Step> type() {
return ValidationStep.class;
}
}
and
public class ProcessConf implements Config {
public Class<? extends Step> type() {
return ProcessStep.class;
}
}
so, the application needs to dynamically instantiate Step subclasses objects, set the configuration accordingly and run the step, like this.
List<Config> configs = loadConfigsFromRepository(); // contain all subtypes of Config
for (Config conf: configs) {
Step<? extends Config> step = conf.type().getDeclaredConstructor().newInstance();
step.setConfig(conf); // compiler complains
}
Error message:
"The method setConfig(capture#8-of ? extends Config) in the type Step<capture#8-of ? extends Config> is not applicable for the arguments (Config)".
Checking the documentation, looks like Java won´t be friendly in this case: https://docs.oracle.com/javase/tutorial/java/generics/wildcardGuidelines.html
What are the possible solutions to overcome this code restriction step.setConfig(conf);
?
EDITED [SOLUTION]
Code can be viewed here: https://github.com/danieldestro/cucumber-salad/tree/generics/src/main/java/my/generics
Upvotes: 1
Views: 139
Reputation: 1964
Because Step.setConfig( Config )
is a „consumer“, one way to resolve the „is not applicable for the arguments (Config)
“ error you get is to use a lower bound like I demonstrate here…
…
List< ? extends Config > configs = loadConfigsFromRepository( ); // contain all subtypes of Config
for ( Config conf: configs ) {
Step< ? super Config > step = conf.type( ).getDeclaredConstructor( ).newInstance( );
step.setConfig( conf ); // *set* makes Step a „consumer“
}
…
That way you don't need the cast that the other answer proposes.
My loadConfigsFromRepository( )
is implemented like…
static List< ? extends Config > loadConfigsFromRepository(){
return of( new ValidationConf( ), new ProcessConf( ) );
}
Upvotes: 2
Reputation: 3030
Get rid of the wildcard. You don't need it.
Step<Config> step = (Step<Config>) conf.type().getDeclaredConstructor().newInstance();
Upvotes: 1
Reputation: 18245
Your approach is not fully correct. I reccomend you not to use Reflections
until you really need it.
Pay attention, that all these implementation should be hidden inside the package and only Step
interface should be public
. Config
implementation holds all data to create Step
class, so just delegate it to this.
package steps
public interface Step<C extends Config> {
void run(Context context);
}
private final class ValidationStep implements Step<ValidationConf> {
private final ValidationConf config;
public ValidationStep(ValidationConf config) {
this.config = config;
}
}
private class ProcessStep implements Step<ProcessConf> {
private final ProcessConf config;
public ValidationStep(ProcessConf config) {
this.config = config;
}
}
public interface Config {
Step<? extends Config> createStep();
}
private class ValidationConf implements Config {
public Step<ValidationConf> createStep() {
return new ValidationStep(this);
}
}
private class ProcessConf implements Config {
public Step<ValidationConf> createStep() {
return new ProcessConf(this);
}
}
package foo
List<Config> configs = loadConfigsFromRepository();
for (Config config : loadConfigsFromRepository()) {
config.createStep().run(context);
}
Upvotes: 0