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 S
es sich um einen Subtyp handelt, eine übergeben T
, wo immer dies T
auftritt S
.
Als solches ist es sicher, eine zurückzugeben, S
wenn 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 AnimalHunter
sollten Sie in der Lage sein, jedes Tier zu jagen, aber gemäß Ihrer Implementierung MammutHunter
akzeptieren Sie nur Mammut
Objekte. 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.