it-swarm.com.de

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

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 an die überschreibende Methode übergebenen Argumente nicht ein Unterklassentyp des erwarteten Supertyps sein können.

Bei der überladenen Methode wird garantiert, dass jede Methode, die ich für das Objekt aufrufe, für das Objekt definiert ist.


Hinweise zu vorgeschlagenen Duplikaten:

Das erster Vorschlag scheint sich mit der Klassenhierarchie und dem Ort der Funktionalität zu befassen. Meine Frage konzentriert sich mehr darauf, warum die Sprachbeschränkung besteht.

Das zweiter Vorschlag erklärt, wie man macht, was ich verlange, aber nicht warum es so gemacht werden muss. Meine Frage konzentriert sich auf das Warum.

12
user1720897

Das Konzept, auf das Sie sich in Ihrer Frage ursprünglich beziehen, heißt kovariante Rückgabetypen .

Kovariante 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 S ein Subtyp von T ist, überall dort, wo T erscheint, ein S übergeben.

Daher ist es sicher, ein S zurückzugeben, wenn eine Methode überschrieben wird, die ein T erwartet.

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.

Einerseits 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 erhalten, da es als solches 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 AnimalHunter sollten Sie in der Lage sein, jedes Tier zu jagen, aber gemäß Ihrer Implementierung von MammutHunter akzeptieren Sie nur Mammut Objekte. Daher erfüllt die Überschreibungsmethode die öffentliche Schnittstelle nicht. Wir haben hier gerade die Solidität des Typsystems gebrochen.

Sie können mithilfe von Generika implementieren, was Sie möchten.

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 implementiert AnimalHunter<Mammal>.

In diesem Fall würde dies nicht akzeptiert:

hunter.hunt(new Mammut()):

Selbst wenn Mammuts Säugetiere sind, wird dies aufgrund der Einschrä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.

18
edalorzo

Das Problem ist nicht, 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, Ihrer Methode zuführen 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 zur Kompilierungszeit bekannt ist. Um eine Methode zu überladen, müssen Sie die Annotation @Override löschen.

3
Florian F