superclase sirve simple que polimorfismo para metodo llamar herencia ejemplos ejemplo como comando clase java oop polymorphism override

java - sirve - Método de superclase que se llama aunque el objeto es de subclase



super en java (6)

Estaba jugando con reglas simples de sobrecarga y encontré algo interesante. Aquí está mi código.

package com.demo; public class Animal { private void eat() { System.out.println("animal eating"); } public static void main(String args[]) { Animal a = new Horse(); a.eat(); } } class Horse extends Animal { public void eat() { System.out.println("Horse eating"); } }

Este programa produce el siguiente.

comer animales

Esto es lo que sé:

  • Como tenemos el método private void eat() , definitivamente no se accederá a él en una subclase, por lo que la cuestión de la anulación del método no se presenta aquí como lo define claramente JLS .
  • Ahora que este no es un método que reemplaza, definitivamente no va a llamar al método public void eat() de la clase Horse.
  • Ahora nuestra declaración Animal a = new Horse(); Es válido por el polimorfismo.

¿Por qué a.eat() invoca un método de la clase Animal ? Estamos creando un objeto Horse , ¿por qué se llama al método de la clase Animal?


En resumen, está sobrecargando el significado previsto de "anulación" en Java :-).

Supongamos que alguien más escribió la clase Animal : (reescribiéndola ligeramente, sin cambiar ninguna semántica, sino para demostrar una buena práctica). También asumiremos que Animal compila y funciona bien:

public class Animal { public static void main(String args[]) { Animal a = new Animal(); // yes, Animal, no Horse yet. a.eat(); } ///// Animal''s private methods, you should not look here private void eat() { System.out.println("animal eating"); } ///// Animal''s private methods, you should not look here }

Esta es una buena práctica de codificación Java porque el autor de Animal clase Animal no quiere que usted, el lector de ese código, sepa realmente nada sobre Animal negocio privado de Animal .

A continuación, observa el método public static void main de Animal e infiere correctamente que existe un método llamado eat() definido allí. En este punto, lo siguiente se mantiene:

  1. Como cualquier otra clase en Java, Animal extiende Object .
  2. Miras los métodos públicos (y protegidos) de Object y encuentras que no existe un método como eat() . Dado que Animal compila bien, ¡puedes inferir que eat debe ser un negocio privado de Animal ! No hay otra forma en que Animal pueda compilar. Por lo tanto, sin mirar el negocio privado de Animal , ¡podría inferir que existe un método eat() en Animal clase Animal que es privado !

Ahora digamos que su intención era crear otro animal llamado Horse como un Animal especializado y darle un comportamiento especial de alimentación. Se da cuenta de que no va a buscar en Java Lang Spec y descubra todas las reglas para hacerlo, y simplemente use la palabra clave extended y termine con ella. Entonces surge la primera versión de Horse . Sin embargo, ha escuchado en alguna parte que es mejor aclarar su intención de anular (esto es algo de lo que ahora está seguro: desea anular el comportamiento alimenticio del Horse ):

class Horse extends Animal { @Override public void eat() { System.out.println("Horse eating"); } }

Derecha; Usted agrega la etiqueta @Override . Esta es siempre una buena idea, es cierto, en un aumento de la verborrea (es una buena práctica por algunas razones que no vamos a entrar aquí).

Intentas compilar Horse.java y ves:

Error:(21, 5) java: method does not override or implement a method from a supertype

Por lo tanto, el compilador que conoce el lenguaje de programación Java mejor que nosotros, nos dice que, de hecho, no estamos anulando o implementando un método que se declara en un supertipo .

Ahora el manejo del reemplazo en Java se vuelve más claro para nosotros. Ya que se supone que solo debemos anular ese comportamiento que está diseñado para anular, es decir, los métodos públicos y protegidos, debemos tener cuidado con la forma en que se escriben las superclases. En este caso, inadvertidamente, la superclase Animal , que aparentemente fue diseñada para la extensión, hizo imposible que las subclases anulen la conducta de eat .

Incluso si @Override etiqueta @Override para eliminar la tecnicidad del error / advertencia del compilador, no necesariamente estaríamos haciendo lo correcto porque en el tiempo de ejecución, como observó, se llama al método no deseado cuya firma coincide. Eso es aún peor.


Lo que probablemente estás pasando por alto aquí: tu método principal está dentro de la clase Animal. Por lo tanto, no es un problema llamar al método privado eat () de la misma clase. Si mueve su método principal a otra clase, ¡encontrará que llamar a eat () en un Animal dará lugar a un error de compilación!

Y, por supuesto, si hubiera puesto la anotación @Override en eat () dentro de Horse, también habría recibido un error de compilación. Porque, como otros han explicado muy bien: no estás anulando nada en tu ejemplo.

Entonces, en esencia:

  1. No estabas anulando nada
  2. No estabas llamando al método que pensabas que estabas llamando

Finalmente, con respecto a tu comentario: por supuesto que hay un objeto Animal . El caballo está extendiendo el animal; por lo que cualquier objeto de caballo es también un objeto animal. Es por eso que pudiste anotar

Animal a = new Horse();

Pero lo importante es entender: después de esa línea, el compilador no sabe más que "a" es en realidad un caballo. Has declarado "a" como animal; y por lo tanto el compilador le permite llamar a los métodos que Animal declara. Tenga en cuenta que la herencia consiste básicamente en describir una relación "IS-A": en su ejemplo, un caballo es un animal.


Los métodos marcados como private no se pueden anular en las subclases porque no son visibles para la subclase. En cierto sentido, su clase de Horse no tiene idea alguna de que Animal tenga un método de eat , ya que está marcado como private . Como resultado, Java no considera que el método de eat del Horse sea ​​una anulación. Esto está diseñado principalmente como una característica de seguridad. Si una clase tiene un método que está marcado como private , se supone que ese método se debe usar solo para los elementos internos de la clase y que es totalmente inaccesible para el mundo exterior. Si una subclase puede anular un método private , entonces podría potencialmente cambiar el comportamiento de una superclase de una manera inesperada, lo cual es (1) no esperado y (2) un riesgo de seguridad potencial.

Debido a que Java supone que un método private de una clase no se anulará, siempre que llame a un método private través de una referencia de algún tipo, Java siempre usará el tipo de referencia para determinar a qué método llamar, en lugar de usar el tipo del objeto señalado por esa referencia para determinar el método a llamar. Aquí, la referencia es de tipo Animal , así que ese es el método al que se llama, aunque esa referencia apunta a un Horse .


Los métodos privados no pueden ser anulados.

http://ideone.com/kvyngL

/* package whatever; // don''t place package name! */ import java.util.*; import java.lang.*; import java.io.*; class Animal { private void eat() { System.out.println("animal eating"); } } class Horse extends Animal { public void eat() { System.out.println("Horse eating"); } } /* Name of the class has to be "Main" only if the class is public. */ class Ideone { public static void main (String[] args) throws java.lang.Exception { // your code goes here Animal a = new Horse(); a.eat(); } }


No estoy seguro si entiendo tu confusión. Basado en lo que sabes:

Tienes razón, Horse.eat() no está anulando a Animal.eat() (ya que es privado). En otras palabras, cuando llama a anAnimal.eat() , no ocurre una vinculación tardía y, por lo tanto, simplemente llama a Animal.eat() , que es lo que ve.

Por tu otro comentario, parece que tu confusión es cómo el compilador está decidiendo a qué llamar. Aquí hay una explicación de muy alto nivel:

Cuando el compilador ve Animal a =...; a.eat(); Animal a =...; a.eat(); , tratará de resolver qué llamar.

Por ejemplo, si ve que eat() es un método estático, dado que es una referencia a Animal , el compilador lo traducirá a Animal.eat() .

Si es un método de instancia y encuentra un método que puede haber sido anulado por una clase hija, lo que hace el compilador es que no generará instrucciones para llamar a un método específico. En su lugar, generará instrucciones para hacer algún tipo de búsqueda desde un vtable. Conceptualmente, cada objeto tendrá una pequeña tabla, cuya clave es la firma del método, y el valor es la referencia al método real a llamar. Por ejemplo, si en su caso, Animal.eat() no es privado, lo que contiene la vtable de Horse es algo como ["eat()" -> "Horse.eat()"] . Así que en tiempo de ejecución , dada una referencia Animal y se llama a eat() , lo que sucede es en realidad: búsqueda desde la tabla del objeto referido con eat() , y se llama el método asociado. (Si la referencia está apuntando a un Horse , el método asociado será Horse.eat() ). Así es como se hace la magia del enlace tardío en la mayoría de los casos.

Con un método de instancia que no se puede anular, los compiladores hacen cosas similares a los métodos estáticos y generan instrucciones para llamar a ese método directamente.

(Lo anterior no es técnicamente exacto, solo una ilustración conceptual para que usted entienda lo que sucedió)


Tu escribiste:

Animal a = new Horse();

En esta situación, un punto apunta al objeto Caballo como si fuera un objeto de tipo Animal .

a.eat() es privado en la clase Animal , por lo que no se puede anular, y es por eso que a.eat() proviene de Animal