Перевизначення методів, передаючи в якості аргументу об'єкт підкласу, де очікується супертип


12

Я просто вивчаю Java, і не є практикуючим програмістом.

У книзі, яку я читаю, йдеться про те, що при перекритті методу типи аргументів повинні бути однаковими, але типи повернення можуть бути поліморфно сумісними.

Моє запитання : чому аргументи, передані методу переходу, не можуть бути типом підкласу очікуваного супертипу?

У перевантаженому методі будь-який метод, який я закликаю до об'єкта, гарантовано визначається на об'єкті.


Примітки до запропонованих дублікатів:

Перше речення , як представляється, про класову ієрархії і де поставити функціональність. Моє питання більш зосереджено на тому, чому існує обмеження мови.

Друга пропозиція пояснює , як зробити те , що я прошу, але не чому це повинно бути зроблено таким чином. Моє питання зосереджено на тому, чому.


1
на запитання та відповідь у попередньому запитанні : "Це, як правило, вважається порушенням принципу заміни Ліскова (LSP), оскільки додане обмеження робить операції базового класу не завжди підходящими для похідного класу ..."
gnat


@gnat питання не в тому, чи вимагає більш конкретних підтипів для аргументів порушує якийсь комп'ютерний принцип, питання полягає в тому, чи можливо це, на що відповів edalorzo.
user949300

Відповіді:


18

Концепція, на яку ви спочатку посилаєтесь у своєму запитанні, називається коваріантними типами повернення .

Коваріантні типи повернення працюють, оскільки метод повинен повертати об'єкт певного типу, а переосмислюючі методи можуть фактично повернути його підклас. Виходячи з правил Sпідтипу такої мови, як Java, якщо це підтип T, тоді, де б не Tз'явилося, ми можемо передати S.

Як такий, можна безпечно повернути знак Sпри перекритті методу, який очікував a T.

Ваша пропозиція визнати, що метод, що переосмислює, використовує аргументи, що є підтипами тих, що запитуються методом переосмисленості, набагато складніше, оскільки це призводить до нерівномірності в системі типів.

З одного боку, за тими ж вищезазначеними правилами підтипів, швидше за все, це вже працює для того, що ви хочете зробити. Наприклад

interface Hunter {
   public void hunt(Animal animal);
}

Ніщо не заважає реалізаціям цього класу отримувати будь-яку тварину, тому що вона вже відповідає критеріям у вашому питанні.

Але припустимо, що ми могли б замінити цей метод, як ви запропонували:

class MammutHunter implements Hunter {
  @Override
  public void hunt(Mammut animal) {
  }
}

Ось найсмішніша частина, тепер ви могли це зробити:

AnimalHunter hunter = new MammutHunter();
hunter.hunt(new Bear()); //Uh oh

Згідно з загальнодоступним інтерфейсом AnimalHunterви повинні мати можливість полювати на будь-яку тварину, але відповідно до вашої реалізації MammutHunterви приймаєте лише Mammutоб'єкти. Тому метод перекриття не задовольняє публічний інтерфейс. Тут ми просто порушили надійність системи типів.

Ви можете реалізувати те, що хочете, використовуючи дженерики.

interface AnimalHunter<T extends Animal> {
   void hunt(T animal);
}

Тоді ви могли б визначити свого MammutHunter

class MammutHunter implements AnimalHunter<Mammut> {
   void hunt(Mammut m){
   }
}

І використовуючи загальну коваріантність та протиріччя, ви можете розслабити правила на свою користь, коли це необхідно. Наприклад, ми могли переконатися, що мисливець на ссавців може полювати тільки на котячих у заданому контексті:

AnimalHunter<? super Feline> hunter = new MammalHunter();
hunter.hunt(new Lion());
hunter.hunt(new Puma());

Припущення MammalHunterзнарядь AnimalHunter<Mammal>.

У такому випадку це не буде прийнято:

hunter.hunt(new Mammut()):

Навіть коли ссавці є ссавцями, це не було б прийнято через обмеження щодо противаріантного типу, який ми тут використовуємо. Отже, ви все ще можете здійснити деякий контроль над типами, щоб робити такі речі, як ті, які ви згадали.


3

Проблема полягає не в тому, що ви робите в переважаючому методі з об'єктом, заданим як аргумент. Проблема полягає в тому, який тип аргументів коду, який використовує ваш метод, дозволяється подавати у ваш метод.

Метод, що переосмислює, повинен відповідати договору перекритого методу. Якщо замінений метод приймає аргументи якогось класу, а ви його замінюєте методом, який приймає лише підклас, він не виконує контракт. Ось чому це не дійсно.

Перевизначення методу з більш конкретним типом аргументу називається перевантаженням . Він визначає метод з тим самим іменем, але з іншим або більш конкретним типом аргументу. Окрім оригінального методу, доступний перевантажений метод. Який метод називається, залежить від типу, відомого під час компіляції. Щоб перевантажити метод, вам потрібно скинути анотацію @Override.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.