Przesłanianie metod przez przekazanie jako argument obiektu podklasy, w którym oczekiwany jest nadtyp

12

Uczę się tylko języka Java i nie jestem praktykującym programistą.

Książka, którą obserwuję, mówi, że podczas nadpisywania metody typy argumentów muszą być takie same, ale typy zwracane mogą być polimorficznie kompatybilne.

Moje pytanie brzmi: dlaczego argumenty przekazane do metody zastępującej nie mogą być podklasą oczekiwanego nadtypu?

W metodzie przeciążonej każda metoda, którą wywołam na obiekcie, ma gwarancję, że zostanie zdefiniowana na obiekcie.


Uwagi na temat sugerowanych duplikatów:

Pierwsza propozycja wydaje się być o klasowej hierarchii i gdzie umieścić funkcjonalność. Moje pytanie jest bardziej skoncentrowane na tym, dlaczego istnieje ograniczenie językowe.

Druga propozycja wyjaśnia, jak zrobić to pytam, ale nie dlatego , że musi być wykonane w taki sposób. Moje pytanie dotyczy tego, dlaczego.

użytkownik1720897
źródło
1
zadane i udzielone w poprzednim pytaniu : „Jest to ogólnie uważane za niezgodne z zasadą podstawienia Liskowa (LSP), ponieważ dodatkowe ograniczenie sprawia, że ​​operacje klasy podstawowej nie zawsze są odpowiednie dla klasy pochodnej ...”
gnat
1
możliwy duplikat
@gnat pytanie nie brzmi, czy wymaganie bardziej szczegółowych podtypów argumentów narusza jakąś zasadę komputerową, pytanie brzmi, czy jest to możliwe, na co odpowiedział Edalorzo.
user949300,

Odpowiedzi:

18

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ś.

edalorzo
źródło
3

Problemem nie jest to, co robisz w metodzie zastępowania z obiektem podanym jako argument. Problem polega na tym, jaki jest typ argumentów, które kod używający twojej metody może przekazywać do twojej metody.

Metoda zastępująca musi spełniać warunki umowy metody zastępowanej. Jeśli przesłonięta metoda akceptuje argumenty jakiejś klasy, a przesłonisz ją za pomocą metody, która akceptuje tylko podklasę, nie spełnia ona umowy. Dlatego nie jest ważny.

Przesłonięcie metody bardziej szczegółowym rodzajem argumentu nazywa się przeciążeniem . Definiuje metodę o tej samej nazwie, ale z innym lub bardziej szczegółowym typem argumentu. Przeciążona metoda jest dostępna oprócz oryginalnej metody. Która metoda jest wywoływana, zależy od typu znanego w czasie kompilacji. Aby przeładować metodę, musisz upuścić adnotację @Override.

Florian F.
źródło