I have a class and an interface like this:
interface Employee {...}
class Developer implements Employee {...}
Then I have classes which implement steps in the onboarding process:
interface OnboardingStep<T extends Employee> {
void perform(T newEmployee);
}
class GeneralOnboardingStep implements OnboardingStep<Employee> {
GeneralOnboardingStep() {}
@Override
void perform(Employee newEmployee) {...}
}
class DeveloperOnboardingStep implements OnboardingStep<Developer> {
DeveloperOnboardingStep() {}
@Override
void perform(Developer newDeveloper) {...}
}
The full onboarding process consists of the general onboarding plus the specific onboarding for developers.
So what I would like to do is this:
abstract class OnboardingProcess<T extends Employee> {
// This is where I need help, see below
private final List<OnboardingStep<T>> onboardingSteps;
protected OnboardingProcess(List<OnboardingStep<T>> onboardingSteps) {
this.onboardingSteps = onboardingSteps;
}
public void perform(T newEmployee) {
for (var onboardingStep: onboardingSteps) {
onboardingStep.perform(newEmployee);
}
}
class DeveloperOnboardingProcess extends OnboardingProcess<Developer> {
DeveloperOnboardingProcess() {
// This does not work
super(List.of(
new GeneralOnboardingStep(),
new DeveloperOnboardingStep()
));
}
}
This does not work, because the List in DeveloperOnboardingProcess
may only contain OnboardingStep<Developer>
and not OnboardingStep<Employee>
.
But it should work because Developer
implements Employee
. (By "should work" I mean: "I wish it worked").
How can I adapt the list type in the abstract class to also accept onboarding steps for the interface?
What you might want is this (but see below):
private final List<OnboardingStep<? super T>> onboardingSteps;
protected OnboardingProcess(List<OnboardingStep<? super T>> onboardingSteps) {
this.onboardingSteps = onboardingSteps;
}
? super T
means that each list element’s perform
method is guaranteed to accept a T instance, since it will be restricting the constructor argument to steps whose type is T or a superclass of T. So, for DeveloperOnboardingProcess, the constructor can take list elements which are an OnboardingStep<Developer>
or an OnboardStep whose generic type is any superclass of Developer, including Employee itself.
However, one thing you cannot do is pass a List<DeveloperOnboardingStep>
. List<DeveloperOnboardingStep>
is not equivalent to List<OnboardingStep<Developer>>
or List<OnboardingStep<Employee>>
, because the latter has an add
(and addAll, etc.) method which allows an OnboardingStep, or a DeveloperOnboardingStep, or any other future subclass of OnboardingStep, whereas a List<DeveloperOnboardingStep>
has an add
method which will accept only a DeveloperOnboardingStep argument.
To allow, say, a List<DeveloperOnboardingStep>
argument, use this:
private final List<? extends OnboardingStep<? super T>> onboardingSteps;
protected OnboardingProcess(List<? extends OnboardingStep<? super T>> onboardingSteps) {
this.onboardingSteps = onboardingSteps;
}