Anulación de métodos pasando como argumento el objeto de subclase donde se espera el supertipo

12

Solo estoy aprendiendo Java, y no soy un programador practicante.

El libro que sigo dice que al anular un método, los tipos de argumento deben ser los mismos, pero los tipos de retorno pueden ser polimórficamente compatibles.

Mi pregunta es ¿por qué los argumentos pasados ​​al método de anulación no pueden ser un tipo de subclase del supertipo esperado?

En el método sobrecargado, se garantiza que cualquier método que invoque en el objeto se definirá en el objeto.


Notas sobre duplicados sugeridos:

La primera sugerencia parece ser sobre la jerarquía de clases y dónde colocar la funcionalidad. Mi pregunta se centra más en por qué existe la restricción de idioma.

La segunda sugerencia explica cómo hacer lo que le pido, pero no por qué tiene que hacerse de esa manera. Mi pregunta se centra en el por qué.

usuario1720897
fuente
1
preguntado y respondido en la pregunta anterior : "Esto generalmente se considera como un incumplimiento del Principio de sustitución de Liskov (LSP), porque la restricción añadida hace que las operaciones de la clase base no siempre sean apropiadas para la clase derivada ..."
gnat
@gnat la pregunta no es si requerir subtipos más específicos para argumentos viola algún principio de la computadora, la pregunta es si es posible, a lo que edalorzo respondió.
user949300
posible duplicado del patrón
mosquito

Respuestas:

18

El concepto al que se refiere inicialmente en su pregunta se llama tipos de retorno covariante .

Los tipos de retorno covariantes funcionan porque se supone que un método devuelve un objeto de cierto tipo y los métodos de anulación en realidad pueden devolver una subclase del mismo. Según las reglas de subtipo de un lenguaje como Java, si Ses un subtipo de T, entonces, donde sea que Taparezca, podemos pasar un S.

Como tal, es seguro devolver un Sal anular un método que esperaba a T.

Su sugerencia de aceptar que una anulación de un método utiliza argumentos que son subtipos de los solicitados por el método anulado es mucho más complicada, ya que conduce a la falta de solidez en el sistema de tipos.

Por un lado, según las mismas reglas de subtipo mencionadas anteriormente, lo más probable es que ya funcione para lo que desea hacer. Por ejemplo

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

Nada impide que las implementaciones de esta clase reciban ningún tipo de animal, por lo que ya satisface los criterios de su pregunta.

Pero supongamos que podríamos anular este método como usted sugirió:

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

Aquí está la parte divertida, ahora puedes hacer esto:

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

Según su interfaz pública AnimalHunter, debería poder cazar cualquier animal, pero según su implementación MammutHunter, solo acepta Mammutobjetos. Por lo tanto, el método reemplazado no satisface la interfaz pública. Acabamos de romper la solidez del sistema de tipos aquí.

Puedes implementar lo que quieras usando genéricos.

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

Entonces podrías definir tu MammutHunter

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

Y utilizando la covarianza y la contravarianza genéricas, puede relajar las reglas a su favor cuando sea necesario. Por ejemplo, podríamos asegurarnos de que un cazador de mamíferos solo pueda cazar felinos en un contexto dado:

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

Suponiendo MammalHunterimplementos AnimalHunter<Mammal>.

En ese caso, esto no sería aceptado:

hunter.hunt(new Mammut()):

Incluso cuando los mamuts son mamíferos, no sería aceptado debido a las restricciones sobre el tipo contravariante que estamos usando aquí. Por lo tanto, aún puede ejercer algún control sobre los tipos para hacer cosas como las que mencionó.

edalorzo
fuente
3

El problema no es lo que haces en el método de anulación con el objeto dado como argumento. El problema es cuál es el tipo de argumentos que el código que usa su método puede alimentar a su método.

Un método de anulación debe cumplir el contrato del método anulado. Si el método anulado acepta argumentos de alguna clase y lo anula con un método que acepta solo una subclase, no cumple el contrato. Por eso no es válido.

Anular un método con un tipo de argumento más específico se llama sobrecarga . Define un método con el mismo nombre pero con un tipo de argumento diferente o más específico. Hay un método sobrecargado disponible además del método original. El método que se llama depende del tipo conocido en tiempo de compilación. Para sobrecargar un método, debe soltar la anotación @Override.

Florian F
fuente