Problem and PECS
The problem here ultimately is a consequence of PECS.
Your instantiatePetServiceByType
method returns a service (PetService<? extends Pet>
) operating on an unknown type. All you know is that it is some sort of pet, but you do not know which specific pet.
Now, when you write
PetService<? extends Pet> petService = instantiatePetServiceByType(petType);
Pet pet = petService.loadPetById(petId);
petService.feed(pet, amount);
Java has a problem with your insertion of the pet in the feed
method, because the method expects from you the exact type that this service operates on. But this type is unknown to you, you can not name it.
Imagine what would happen if someone writes:
PetService<? extends Pet> petService = instantiatePetServiceByType(petType);
Pet pet = petService.loadPetById(petId);
// Malicious
pet = new Cat();
petService.feed(pet, amount);
Now, pet
is a Cat
, but the service operates perhaps on Dog
s. So if Java would allow your code to compile, users could do above and break the type system.
As a consequence of this dilemna, PECS just completely disallows you to call the feed
method all-together, because it is impossible for you to ensure that you supply the correct type. Or rather, it is impossible for the compiler to ensure that you are not messing with it.
Solution
In fact, since you want to find the service dynamically during runtime, it is unfortunately impossible to make the code type-safe. Generics are a compile-time feature and at the point where you know the type of the service (runtime), it is already too late.
Fortunately, there is a way to give the illusion of type-safety to the user and still providing a sane generic system, by dropping type-safety inside your instantiatePetServiceByType
method.
The idea goes as follows:
- let the user tell you which type they expect
- assume it is correct - if not, crash during runtime
- return the service casted to the expected type
<T extends Pet> PetService<T> instantiatePetServiceByType(Class<T> petType) {
PetService<? extends Pet> service = ... // find your service
return petType.cast(service);
}
For the user, the generics work and are convenient to use:
PetService<Dog> dogService = api.instantiatePetServiceByType(Dog.class);
Dog dog = dogService.loadPetById(petId);
dogService.feed(dog, amount);
Now you just have to make sure that you never save incorrect services. For example registering a PetService<Cat>
as a service for Dog
s.