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 S
jest podtypem T
, to gdziekolwiek się T
pojawi, możemy przekazać S
.
W związku z tym można bezpiecznie zwrócić wartość, S
gdy 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 AnimalHunter
powinieneś być w stanie polować na dowolne zwierzę, ale zgodnie z twoją implementacją MammutHunter
akceptujesz tylko Mammut
przedmioty. 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 MammalHunter
narzę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ś.