Pojęcie, do którego początkowo odwołujesz się w pytaniu, nazywa się kowariantnymi typami zwrotu .
Kowariantne typy zwracania działają, ponieważ metoda ma zwrócić obiekt określonego typu, a metody zastępujące mogą faktycznie zwrócić jego podklasę. Oparty na regułach podtypów języka takiego jak Java, jeśli Sjest podtypem T, to gdziekolwiek się Tpojawi, możemy przekazać S.
W związku z tym można bezpiecznie zwrócić wartość, Sgdy przesłaniana jest metoda, która oczekiwała a T.
Twoja sugestia, aby zaakceptować, że przesłonięcie metody wykorzystuje argumenty, które są podtypami żądanych przez przesłoniętą metodę, jest o wiele bardziej skomplikowana, ponieważ prowadzi do nieuczciwości w systemie typów.
Z jednej strony, zgodnie z tymi samymi regułami podtytułu, o których mowa powyżej, najprawdopodobniej już działa dla tego, co chcesz zrobić. Na przykład
interface Hunter {
public void hunt(Animal animal);
}
Nic nie stoi na przeszkodzie, aby implementacje tej klasy otrzymywały jakiekolwiek zwierzę, ponieważ spełnia ono kryteria określone w pytaniu.
Załóżmy jednak, że moglibyśmy zastąpić tę metodę, jak zasugerowałeś:
class MammutHunter implements Hunter {
@Override
public void hunt(Mammut animal) {
}
}
Oto zabawna część, teraz możesz to zrobić:
AnimalHunter hunter = new MammutHunter();
hunter.hunt(new Bear()); //Uh oh
Zgodnie z publicznym interfejsem AnimalHunterpowinieneś być w stanie polować na dowolne zwierzę, ale zgodnie z twoją implementacją MammutHunterakceptujesz tylko Mammutprzedmioty. Dlatego metoda przesłonięcia nie spełnia interfejsu publicznego. Właśnie złamaliśmy tutaj solidność systemu typów.
Możesz zaimplementować to, co chcesz, używając ogólnych.
interface AnimalHunter<T extends Animal> {
void hunt(T animal);
}
Następnie możesz zdefiniować swojego MammutHunter
class MammutHunter implements AnimalHunter<Mammut> {
void hunt(Mammut m){
}
}
Za pomocą ogólnej kowariancji i kontrawariancji możesz w razie potrzeby rozluźnić reguły na swoją korzyść. Na przykład możemy upewnić się, że łowca ssaków może polować na koty tylko w określonym kontekście:
AnimalHunter<? super Feline> hunter = new MammalHunter();
hunter.hunt(new Lion());
hunter.hunt(new Puma());
Przypuśćmy, że MammalHunternarzędzia AnimalHunter<Mammal>.
W takim przypadku nie zostanie to zaakceptowane:
hunter.hunt(new Mammut()):
Nawet gdy ssaki są ssakami, nie byłoby to akceptowane z powodu ograniczeń, jakie stosujemy w tym typie przeciwstawnym. Tak więc nadal możesz ćwiczyć kontrolę nad typami, aby robić rzeczy takie jak te, o których wspomniałeś.