que method java overloading

method - overriding java



¿Cuál es el razonamiento detrás de no permitir supertipos en anulaciones de métodos Java? (7)

El siguiente código es considerado inválido por el compilador:

class Foo { void foo(String foo) { ... } } class Bar extends Foo { @Override void foo(Object foo) { ... } }

Creo que esto se describe en JLS 8.4.8.1 : "La firma de m1 es una subscripción (§8.4.2) de la firma de m2". y en 8.4.2: "los límites de las variables de tipo correspondientes son los mismos".

Mi pregunta es: ¿por qué el parámetro en el subtipo (Bar) no puede ser un supertipo del parámetro en el supertipo (Foo)? En el ejemplo, Object es un supertipo de String. Por lo que puedo ver, permitir esto no violaría el Principio de Sustitución de Liskov .

¿Existe un escenario en el que permitir esto rompa el código o es una limitación del JLS actual?


(Una reescritura, desde un ángulo diferente ... mi respuesta original contenía un error. :()

por qué el parámetro en el subtipo (Bar) no puede ser un supertipo del parámetro en el supertipo (Foo).

Creo que, técnicamente, podría y no rompería los contratos de los antepasados ​​después de una subestación de tipo (principio de sustitución de Liskov).

  • Los tipos se pasan por valor (incluidos los tipos de referencia).
  • La persona que llama nunca puede ser obligada a lidiar con diferentes tipos de parámetros de los que pasa.
  • Un cuerpo de método puede intercambiar un tipo de parámetro, pero no puede devolverlo a la persona que llama (no existe tal cosa como ''tipos de parámetros de salida'').
  • Si su propuesta fue permitida y se realizó una llamada a la firma del método del antepasado, una clase decendente podría anular el método con tipos más amplios, pero aún así es imposible devolver un tipo más amplio que el que establece la persona que llama.
  • La anulación nunca podría interrumpir a un cliente que usa el contrato de método de antepasado limitado.

De mi análisis a continuación, conjeturo / adivino la razón para no permitir su escenario:

  • Relacionado con el rendimiento : permitir tipos más amplios de anulación afectaría el rendimiento en tiempo de ejecución, un problema importante
  • Relacionado con la funcionalidad : solo agrega una pequeña cantidad de funcionalidad. Tal como está, podría agregar su ''método más amplio'' como un método sobrecargado sin anular. Luego, también puede anular el método original con una coincidencia de firma exacta. El resultado neto: logras algo bastante similar, funcionalmente.

Requisitos del compilador para la anulación del método - JLS 7

El compilador debe actuar de acuerdo con su experiencia. 8.4 Declaraciones del método :

Un método en una subclase puede anular un método en una clase ancestral iff:

  • los nombres de los métodos son idénticos ( 8.4.2 y 8.4.8.1 )
  • los parámetros del método tienen tipos idénticos, después del borrado de los parámetros de tipo genérico ( 8.4.2 y 8.4.8.1 )
  • el tipo de retorno es sustituible por el tipo de retorno en la clase ancestral, es decir, el mismo tipo o más estrecho ( 8.4.8.3 )

    Nota: la subscripción no significa que el método de reemplazo usa subtipos del método reemplazado. Se dice que el método de anulación tiene una subscripción del método anulado cuando el método de anulación tiene exactamente la misma firma de tipo, excepto que los tipos genéricos y los tipos brutos correspondientes se consideran equivalentes.

Compilador v Procesamiento en tiempo de ejecución para el método de coincidencia e invocación

Hay un rendimiento que iguala las firmas del método de coincidencia a través de la coincidencia de tipos polimórficos. Al restringir las firmas de los métodos de anulación a la coincidencia exacta del ancestro, el JLS mueve gran parte de este procesamiento al tiempo de compilación. 15.12 Expresiones de invocación del método - resumidas:

  1. Determine la clase o la interfaz a buscar (determinación en tiempo de compilación)

    • Obtenga el tipo base T, en el que se invoca el método.
    • Este es el tipo de referencia declarado al compilador y no el tipo de tiempo de ejecución (donde se puede sustituir un subtipo).
  2. Determinar la firma del método (determinación de tiempo de compilación)

    • Busque en el tipo base T de tiempo de compilación, los métodos aplicables que coincidan con el nombre y el parámetro y los tipos de retorno que concuerden con la llamada.
      • resuelva los parámetros genéricos para T, ya sea explícitamente pasados ​​o implícitamente inferidos de los tipos de argumentos de los métodos de invocación
      • Etapa 1: métodos aplicables a través de tipos / subtipos consistentes (''subtipos'')
      • Etapa 2: métodos aplicables a través del boxeo automático / unboxing más subtipos
      • Etapa 3: métodos aplicables a través de boxeo automático / unboxing más subtipos más variables ''arity'' parámetros
      • determine la firma de método de coincidencia más específica (es decir, la firma de método que podría pasarse con éxito a todas las demás firmas de método de coincidencia); si no hay: compilador / error de ambigüedad
  3. Verifique: ¿Es apropiado el método elegido? (Determinación de tiempo de compilación)

  4. Evaluación de invocación de método (determinación de tiempo de ejecución)

    • Determine el tipo de referencia de destino en tiempo de ejecución
    • Evaluar argumentos
    • Verificar accesibilidad del método.
    • Método de localización: una coincidencia exacta de la firma con la firma coincidente en tiempo de compilación
    • Invocar

Hit de rendimiento

El desglose, en términos de texto en el JLS:

Paso 1: 5% Paso 2: 60% Paso 3: 5% Paso 4: 30%

El Paso 2 no solo es voluminoso en texto, sino que es sorprendentemente complejo. Tiene condiciones complejas y muchas condiciones de prueba / búsquedas costosas. Es ventajoso maximizar la ejecución del compilador del procesamiento más complejo y más lento aquí. Si esto se hiciera en tiempo de ejecución, habría un arrastre en el rendimiento, ya que se produciría para cada invocación de método.

El paso 4 todavía tiene un procesamiento significativo, pero es lo más sencillo posible . Al leer 15.12.4 , no contiene pasos de procesamiento que puedan moverse al tiempo de compilación, sin forzar que el tipo de tiempo de ejecución coincida exactamente con el tipo de tiempo de compilación. No solo eso, sino que hace una coincidencia exacta simple en la firma del método, en lugar de una "coincidencia de tipo ancestro" compleja


El problema con su código es que le está diciendo al compilador que el método foo en la clase Bar está anulado de la clase padre de la barra, es decir, Foo. Dado que los overridden methods must have same signature , pero en su caso, según la sintaxis, es un método sobrecargado, ya que ha cambiado el parámetro en el método foo de la clase Bar.


La respuesta es sencilla: en Java, para la sustitución de métodos, debe tener la firma exacta del supertipo. Sin embargo, si elimina la anotación @Override , su método se sobrecargará y su código no se romperá. Esta es una implementación de Java que garantiza que te refieres a que la implementación del método debe anular la implementación del supertipo.

El método de anulación funciona de la siguiente manera.

class Foo{ //Super Class void foo(String string){ // Your implementation here } } class Bar extends Foo{ @Override void foo(String string){ super(); //This method is implied when not explicitly stated in the method but the @Override annotation is present. // Your implementation here } // An overloaded method void foo(Object object){ // Your implementation here } }

Los métodos que se muestran arriba son correctos y su implementación puede variar.

Espero que esto te ayude.


Las anotaciones de Java no pueden cambiar la forma en que el compilador genera el código de bytes de sus clases. Utiliza las anotaciones para decirle al compilador cómo cree que debería interpretarse su programa, e informa los errores cuando la interpretación del compilador no coincide con sus intenciones. Sin embargo, no puede usar anotaciones para forzar al compilador a producir código con semántica diferente.

Cuando su subclase declara un método con el mismo nombre y número de parámetros que en su superclase, Java debe decidir entre dos posibilidades:

  • Desea que el método en la subclase anule el método en la superclase, o
  • Desea que el método en la subclase sobrecargue el método en la superclase.

Si Java permitiera que foo(Object) anulara a foo(String) el lenguaje tendría que introducir una sintaxis alternativa para indicar la intención de sobrecargar un método. Por ejemplo, podrían haberlo hecho de una manera similar a la new y override C # en las declaraciones de métodos. Sin embargo, por la razón que sea, los diseñadores decidieron no utilizar esta nueva sintaxis, dejando el lenguaje con las reglas especificadas en JLS 8.4.8.1.

Tenga en cuenta que el diseño actual le permite implementar la funcionalidad de una anulación al reenviar la llamada desde la función sobrecargada. En su caso, eso significaría llamar a Bar.foo(Object) desde Foo.foo(String) esta manera:

class Foo { public void foo(String foo) { ... } } class Bar extends Foo { @Override public void foo(String foo) { this.foo((Object)foo); } public void foo(Object foo) { ... } }

Aquí hay una demostración de ideone .


Para responder a la pregunta en el título de la publicación, What is the reasoning behind not allowing supertypes on Java method overrides? :

Los diseñadores de Java querían un lenguaje orientado a objetos simple y rechazaban específicamente las características de C ++ donde, en su opinión, la complejidad / las dificultades de la característica no valían la pena. Lo que describe puede haber caído en esta categoría en la que los diseñadores eligieron diseñar / especificar la característica.


Si puede anular con superclases, ¿por qué no con subclases también?

Considera lo siguiente:

class Foo { void foo(String foo) { ... } } class Bar extends Foo { @Override void foo(Object foo) { ... } } class Another extends Bar { @Override void foo(Number foo) { ... } }

Ahora ha anulado correctamente un método cuyo parámetro original era una String para aceptar un Number . Inadvisible para decir lo menos ...

En cambio, los resultados deseados se pueden replicar utilizando la sobrecarga y el siguiente código, más explícito:

class Foo { void foo(String foo) { ... } } class Bar extends Foo { @Override private void foo(String foo) { ... } void foo(Object foo) { ... } } class Another extends Bar { @Override private void foo(Object foo) { ... } void foo(Number foo) { ... } }


Supongamos que podrías hacer eso. Ahora tu superclase se ve así:

class Foo { void foo(String foo) { ... } void foo(Number foo) { ... } }

y tu subclase ahora:

class Bar extends Foo { @Override void foo(Object foo) { ... } }

El lenguaje probablemente podría permitir tal cosa (y simplemente enviar Foo.foo (String) y Foo.foo (Number) a Bar.foo (Object)), pero aparentemente, la decisión de diseño para Java aquí es que un solo método puede anular exactamente otro método.

[Editar]

Como dijo dasblinkenlight en su respuesta, uno puede tener un foo (Objeto) sin el @Override, pero esto solo sobrecarga las funciones de foo, y no las anula. Al llamar, java elige el método más específico, por lo que foo ("Hello World") siempre se enviará al método foo (String).