I am working on a project where services need to be added to a component. The Service
class is an interface without any methods. Here is an example how my services work:
public interface Service { }
public interface CarWash extends Service {
void washCar(Car car);
}
public interface CarRepair extends Service {
void repairCar(Car car);
}
Now there are many implementations of these services. A single class may implement multiple services, as this garage class:
public class Garage implements CarWash, CarRepair {
@Override
public void washCar(Car car) { /* .. */ }
@Override
public void repairCar(Car car) { /* .. */ }
}
When adding a service to a component, I do not want to need to use the service for all tasks, but for example use the Garage
only for washing cars (CarWash
) but not for repairing them (CarRepair
). Therefore I specify the tasks as classes, just like this:
void addService(Service service, Class<? extends Service> task);
To check whether the service could actually perform the task, I used generics:
<T extends Service> addService(T service, Class<? super T> task);
This works well but does not check if the provided task is actually a task (a class that implements Service
), so this would work:
addService(myTask, Object.class);
I am looking for a way to specify that service
needs to implement (extend) the task
AND the task
is extending the Service
interface, like this (does not compile):
<T extends Service> addService(T service, Class<? super T extends Service> task);
We can add generic type parameters to class methods, static methods, and interfaces. Generic classes can be extended to create subclasses of them, which are also generic.
The polymorphism applies only to the 'base' type (type of the collection class) and NOT to the generics type.
I think that <T extends Service, S extends T> void addService(S service, Class<T> clazz)
sounds like it meets your criteria:
public static class Foo {
public interface Service { }
public interface CarWash extends Service {
void washCar();
}
public interface CarRepair extends Service {
void repairCar();
}
static <T extends Service, S extends T> void addService(S service, Class<T> clazz) {}
public static void main(String[] args) {
addService(null, CarWash.class); // Fine.
addService(null, Object.class); // Compilation error.
}
}
(I added some statics and removed Car
from method signatures, since I don't have a definition of those to compile)
This may also be fine, depending on how you use the type of service
:
<T extends Service> void addService(T service, Class<T> task)
If the service
is a subtype of the type represented by task
, then it can always be upcasted.
addService(new Garage(), CarWash.class); // OK, Garage is a CarWash
The only problem I am having is the varargs that force all array elements to be of the same type, and therefore I cannot write
addService(null, CarWash.class, CarRepair.class);
This is actually a difficult problem for Java generics. (C++ can do this with variadic templates, which is a feature Java is very unlikely to get.)
So one way you can solve this in Java is with run time validation e.g.:
<T extends Service> void addService(
T service, Class<? super T> tasks...) {
for(Class<? super T> task : tasks)
if(!Service.class.isAssignableFrom(tasks))
throw new IllegalArgumentException();
}
(Or use Class<? extends Service>
and check that task.isInstance(service)
.)
But I know we don't really like that. ; )
Java does have something called an intersection type (where if we have a type <? extends T & U>
, the T & U
part is called an intersection type), but an intersection type cannot combine super
and extends
and they are otherwise pretty limited in what else they can do.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With