英文:
Why can i not use a generic type as parameter in Java?
问题
在Java中,我有一个带有泛型类型的接口,该泛型类型还实现了不同的接口。
当我将这个泛型用作参数时,我无法传递接口的实例,因为它的类型不正确。
请看这个例子:
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) {
// 从数据库加载并返回具有此ID的狗
}
@Override
public void feed(Dog pet, int amount) {
// 喂狗
}
}
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) {
// 超出范围
}
}
现在是我的问题:
在IDE中对petService.feed(pet, amount)
的调用会出现错误,指出pet
不是正确的类型。
它说:
- 所需类型:?的捕获
- 提供的类型:Ownable
我做错了什么?
英文:
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:
- Required type: capture of ?
- Provided: Ownable
What am i doing wrong?
答案1
得分: -1
问题和PECS
这里的问题最终是PECS的一个后果。
您的instantiatePetServiceByType
方法返回一个在未知类型上操作的服务(PetService<? extends Pet>
)。您只知道它是某种宠物,但不知道是哪种具体的宠物。
现在,当您写下以下代码时:
PetService<? extends Pet> petService = instantiatePetServiceByType(petType);
Pet pet = petService.loadPetById(petId);
petService.feed(pet, amount);
Java在feed
方法中插入宠物时会出现问题,因为该方法从您那里期望这个服务操作的确切类型。但这个类型对您来说是未知的,您无法_命名_它。
想象一下,如果有人这样写:
PetService<? extends Pet> petService = instantiatePetServiceByType(petType);
Pet pet = petService.loadPetById(petId);
// 恶意操作
pet = new Cat();
petService.feed(pet, amount);
现在,pet
是一个Cat
,但该服务可能是针对Dog
的。因此,如果Java允许您的代码编译,用户可以执行上述操作并破坏类型系统。
由于这个困境,PECS完全不允许您调用feed
方法,因为您无法确保提供了正确的类型。或者说,编译器无法确保您没有对其进行操作。
解决方案
实际上,由于您希望在运行时动态查找服务,因此无法使代码类型安全。泛型是一种编译时功能,而在您知道服务类型的时候(运行时),已经为时太晚。
幸运的是,有一种方法可以给用户提供类型安全的假象,并仍然提供一个明智的泛型系统,方法是在您的instantiatePetServiceByType
方法内部放弃类型安全。
思路如下:
- 让用户告诉您他们期望的类型
- 假设它是正确的 - 如果不是,在运行时崩溃
- 返回转换为期望类型的服务
<T extends Pet> PetService<T> instantiatePetServiceByType(Class<T> petType) {
PetService<? extends Pet> service = ... // 查找您的服务
return petType.cast(service);
}
对于用户来说,泛型是有效且方便的:
PetService<Dog> dogService = api.instantiatePetServiceByType(Dog.class);
Dog dog = dogService.loadPetById(petId);
dogService.feed(dog, amount);
现在,您只需要确保不要保存不正确的服务。例如,将PetService<Cat>
注册为Dog
的服务。
英文:
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论