In Java i have an interface that has a generic type that also implements a different interface.
When i use this generic as a parameter, i cannot pass an instance of the interface because it is of the wrong type.
See this example:
interface Pet {}
interface PetService<T extends Pet> {
T loadPetById(int petId);
void feed(T pet, int amount);
}
class Dog implements Pet {}
class DogService implements PetService<Dog> {
@Override
public Dog loadPetById(int petId) {
// load and return dog with this id from database
}
@Override
public void feed(Dog pet, int amount) {
// feed dog
}
}
class RestApi {
void feedPet(String petType, int petId, int amount) {
PetService<? extends Pet> petService = instantiatePetServiceByType(petType);
Pet pet = petService.loadPetById(petId);
petService.feed(pet, amount);
}
PetService<? extends Pet> instantiatePetServiceByType(String petType) {
// out of scope
}
}
Now to my problem:
The call to petService.feed(pet, amount)
complains in the IDE that pet
is not the correct type.
It says:
What am i doing wrong?
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.
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:
<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.