Überschreiben von Methoden durch Übergeben des Unterklassenobjekts als Argument, in dem der Supertyp erwartet wird

12

Ich lerne gerade Java und bin kein praktizierender Programmierer.

Das Buch, dem ich folge, besagt, dass beim Überschreiben einer Methode die Argumenttypen identisch sein müssen, die Rückgabetypen jedoch polymorph kompatibel sein können.

Meine Frage ist, warum die Argumente, die an die überschreibende Methode übergeben werden, keine Unterklassen des erwarteten Supertyps sein können.

In der überladenen Methode wird garantiert, welche Methode ich für das Objekt aufrufe, für das Objekt definiert.


Anmerkungen zu vorgeschlagenen Duplikaten:

Der erste Vorschlag scheint sich mit der Klassenhierarchie und dem Ort zu befassen, an dem die Funktionalität bereitgestellt werden soll. Meine Frage konzentriert sich eher darauf, warum die Spracheinschränkung besteht.

Der zweite Vorschlag erklärt, wie man das macht, wonach ich frage, aber nicht, warum es so gemacht werden muss. Meine Frage konzentriert sich auf das Warum.

user1720897
quelle
1
in der vorherigen Frage gestellt und beantwortet : "Dies wird im Allgemeinen als Verstoß gegen das Liskov-Substitutionsprinzip (LSP) angesehen, da die hinzugefügte Einschränkung dazu führt, dass die Basisklassenoperationen nicht immer für die abgeleitete Klasse geeignet sind ..."
gnat
@gnat die Frage ist nicht, ob das Erfordernis spezifischerer Untertypen für Argumente gegen ein Computerprinzip verstößt, die Frage ist, ob es möglich ist, welche edalorzo beantwortet.
user949300

Antworten:

18

Das Konzept, auf das Sie sich in Ihrer Frage zunächst beziehen, heißt kovariante Rückgabetypen .

Covariante Rückgabetypen funktionieren, weil eine Methode ein Objekt eines bestimmten Typs zurückgeben soll und überschreibende Methoden möglicherweise tatsächlich eine Unterklasse davon zurückgeben. Basierend auf den Subtypisierungsregeln einer Sprache wie Java können wir , wenn Ses sich um einen Subtyp handelt, eine übergeben T, wo immer dies Tauftritt S.

Als solches ist es sicher, eine zurückzugeben, Swenn eine Methode überschrieben wird, die a erwartet T.

Ihr Vorschlag, zu akzeptieren, dass beim Überschreiben einer Methode Argumente verwendet werden, die Untertypen der von der überschriebenen Methode angeforderten sind, ist viel komplizierter, da dies zu Unklarheiten im Typsystem führt.

Auf der einen Seite funktioniert es nach den oben genannten Subtypisierungsregeln höchstwahrscheinlich bereits für das, was Sie tun möchten. Zum Beispiel

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

Nichts hindert Implementierungen dieser Klasse daran, irgendeine Art von Tier zu empfangen, da dies bereits die Kriterien in Ihrer Frage erfüllt.

Nehmen wir jedoch an, wir könnten diese Methode überschreiben, wie Sie vorgeschlagen haben:

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

Hier ist der lustige Teil, jetzt können Sie dies tun:

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

Gemäß der öffentlichen Schnittstelle von AnimalHuntersollten Sie in der Lage sein, jedes Tier zu jagen, aber gemäß Ihrer Implementierung MammutHunterakzeptieren Sie nur MammutObjekte. Daher erfüllt die Überschreibungsmethode die öffentliche Schnittstelle nicht. Wir haben hier nur die Solidität des Typsystems gebrochen.

Sie können das implementieren, was Sie möchten, indem Sie Generika verwenden.

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

Dann könnten Sie Ihren MammutHunter definieren

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

Und mit generischer Kovarianz und Kontravarianz können Sie die Regeln bei Bedarf zu Ihren Gunsten lockern. Zum Beispiel könnten wir sicherstellen, dass ein Säugetierjäger nur in einem bestimmten Kontext Katzen jagen kann:

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

Angenommen MammalHunter, Anbaugeräte AnimalHunter<Mammal>.

In diesem Fall würde dies nicht akzeptiert:

hunter.hunt(new Mammut()):

Selbst wenn es sich bei Mammuts um Säugetiere handelt, würde dies aufgrund der Beschränkungen des hier verwendeten kontravarianten Typs nicht akzeptiert. Sie können also immer noch eine gewisse Kontrolle über die Typen ausüben, um Dinge wie die von Ihnen erwähnten zu tun.

edalorzo
quelle
3

Das Problem ist nicht das, was Sie in der überschreibenden Methode mit dem als Argument angegebenen Objekt tun. Das Problem ist, welche Art von Argumenten der Code, der Ihre Methode verwendet, in Ihre Methode einspeisen darf.

Eine überschreibende Methode muss den Vertrag der überschriebenen Methode erfüllen. Wenn die überschriebene Methode Argumente einer Klasse akzeptiert und Sie sie mit einer Methode überschreiben, die nur eine Unterklasse akzeptiert, erfüllt sie den Vertrag nicht. Deshalb ist es nicht gültig.

Das Überschreiben einer Methode mit einem spezifischeren Argumenttyp wird als Überladen bezeichnet . Es definiert eine Methode mit demselben Namen, jedoch mit einem anderen oder spezifischeren Argumenttyp. Neben der ursprünglichen Methode steht eine überladene Methode zur Verfügung. Welche Methode aufgerufen wird, hängt vom Typ ab, der zum Zeitpunkt der Kompilierung bekannt ist. Um eine Methode zu überladen, müssen Sie die @ Override-Annotation löschen.

Florian F
quelle