Концепция, на которую вы изначально ссылаетесь в своем вопросе, называется ковариантными типами возврата .
Ковариантные возвращаемые типы работают, потому что метод должен возвращать объект определенного типа, а переопределяющие методы могут фактически возвращать его подкласс. Основываясь на правилах подтипирования языка, такого как Java, если S
это подтип 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()):
Даже если мамонты являются млекопитающими, это не будет принято из-за ограничений на контравариантный тип, который мы здесь используем. Таким образом, вы все еще можете получить некоторый контроль над типами, чтобы делать вещи, подобные тем, которые вы упомянули.