La herencia en Java: qué se sobreescribe y qué no

La herencia en Java puede resultar en ocasiones bastante compleja. Qué se sobreescribe y qué se oculta, cuándo se instancia cada clase, qué puede suponer un problema a la hora de compilar... En este artículo, el primero de varios sobre la herencia en Java, te voy a explicar cuáles son los detalles a los que tienes que prestar atención para que no te sorprendan en el examen de certificación.

More...

Lo primero es lo primero: qué se extiende y qué se implementa.

La herencia en Java: extends e implements

Cuando te encuentres con alguna pregunta sobre herencia en el examen de certificación, fíjate en las palabras clave que se utilizan, y el orden en el que están. Recuerda estos dos puntos:

  • Igual extends igual. Distinto implements distinto. Esto quiere decir que una clase extiende otra clase, pero una clase implementa una interfaz. De igual modo, una interfaz extiende otra interfaz. Por último, una interfaz no puede heredar de una clase.
  • Una clase puede extender una clase y/o implementar muchas interfaces. Una interfaz puede extender muchas interfaces. Con las interfaces no hay límite, recuérdalo.
  • El orden de las palabras clave cuando una clase extiende otra clase e implementa una o varias interfaces es extends-implements. Si te paras a pensar, tiene lógica: primero colocas la única clase que se puede extender, y a continuación la lista ilimitada de interfaces que se implementen.

En este ejemplo de herencia en Java, he creado un objeto de tipo Coche, que hereda de la clase Vehículo (es decir, un Coche es también un Vehículo). Como interfaces, lo he equipado con GPS y frenos ABS.

La herencia en Java: qué se sobreescribe y qué se oculta

Sigamos con el ejemplo de la imagen anterior. Le voy a ir añadiendo métodos y variables y vamos a ver qué se sobreescribe, y qué se oculta.

¿Qué diferencia hay entre sobreescribir y ocultar?

Esto es algo que tienes que tener muy claro a la hora de entender la herencia en Java.

Imagínate que A es la superclase y B, la subclase. Es decir, B extends A.

Como ves, he creado tres objetos: dos objetos de tipo B, con referencias de tipo A y B, respectivamente, y un objeto de tipo A, con una referencia de tipo A.

B oculta un método o variable de A

Si un método de B oculta un método estático (o una variable) de A, ocurre lo siguiente:

  • check
    El objeto de tipo B instanciado con una referencia de tipo A va a ejecutar el método de la clase A
  • check
    El objeto de tipo B instanciado con una referencia de tipo B va a ejecutar el método de la clase B.
  • check
    El objeto de tipo A instanciado con una referencia de tipo A va a ejecutar el método de la clase A.

Conclusión: cuando una variable o un método estático se ocultan, cada objeto va a utilizar el método o la variable que aparezca en la clase de su referencia.

B sobreescribe un método de A

Si un método de B sobreescribe un método no estático de A (las variables no se pueden sobreescribir), ocurre esto otro:

  • check
    El objeto de tipo B instanciado con una referencia de tipo A va a ejecutar el método de la clase B
  • check
    El objeto de tipo B instanciado con una referencia de tipo B va a ejecutar el método de la clase B
  • check
    El objeto de tipo A instanciado con una referencia de tipo A va a ejecutar el método de la clase A

Conclusión: cuando un método se sobreescribe, cada objeto va a utilizar el método no estático que aparezca en su propia clase, independientemente del tipo de referencia utilizado al instanciarlo.

Como ves, cuando el objeto instanciado utiliza una referencia de su propia clase, va a ejecutar siempre el código de su clase. El objeto de tipo A con una referencia de tipo A va a ejecutar el código de la clase A. Por su parte, el objeto de tipo B con una referencia de tipo B va a hacer lo propio con el código de su clase. Son los objetos híbridos que, aún siendo de tipo subclase, se han instanciado con una referencia de la superclase, los que varían su comportamiento.

Si te parece un poco confuso, no te preocupes: con los siguientes ejemplos te quedará muy claro.

Variables estáticas y no estáticas: se ocultan

Las variables nunca se sobreescriben en Java. Esto quiere decir que la clase Coche no tiene acceso a las variables de la clase Vehiculo si las oculta con su propia versión de estas variables:

Como ves, en el método main() de la clase Coche he creado dos objetos de tipo Coche, pero mientras que al primero apunta una referencia de tipo Coche, al segundo apunta una referencia de tipo Vehiculo. Como ves, el acceso a las variables depende del tipo de referencia, no del tipo de objeto que instanciemos. Al ocultarse las variables, la referencia de tipo Coche solo tiene acceso a las variables de su clase, ya sean estáticas o no.

Métodos estáticos: se ocultan

Al igual que en el caso de las variables, los métodos estáticos se ocultan, de manera que cada referencia tiene acceso a los métodos estáticos de su propia clase, independientemente del tipo de objeto al que apunten estas referencias.

Al ejecutar el código, la referencia de tipo Vehiculo ejecuta el método estático de la clase Vehiculo, mientras que la referencia Coche ejecuta el método estático de la clase Coche. Aquí puedes tú mismo probar muchas combinaciones (eliminar uno de los dos métodos, o una de las dos variables); lo único que tienes que tener claro es que la referencia de tipo Vehiculo solo va a acceder al código contenido en la clase Vehiculo. Coche ejecuta el método de Vehiculo si no lo oculta con su propio método.

El método estático de Coche puede imprimir la variable de Vehiculo si la nombra de manera explícita:

Métodos no estáticos: se sobreescriben

Aquí es donde empieza la confusión. Sí, porque aunque puedas adivinar que, si se sobreescriben los métodos, ambas referencias van a ejecutar el mismo código, ¿qué pasa si se imprimen las variables? ¿Qué variables se van a imprimir, si están ocultas? ¿Cada referencia las de su clase, o ambas la de la subclase?

¡Exacto! Dado que la variable de la clase Coche oculta la variable de la clase Vehiculo, estaba claro que el método de la clase Coche, que ejecutan ambas referencias, solo podía ver la variable de su misma clase.

¿Se puede eliminar un método sobreescrito?

Es posible que te preguntes si, ahora que el método de la clase Coche es el que ejecutan ambas referencias, puedes eliminar el método de la clase Vehiculo. Probemos:

¡No puedes! ¿Sabes por qué? Porque, para que te hagas una idea, la referencia de tipo Vehiculo sigue ejecutando el código de su clase. Lo que ocurre es que la clase Coche, al crear su propio método, ha entrado en la clase Vehiculo y ha escrito encima su propio método. Es como el cuco que mete sus huevos en nido ajeno, dando el cambiazo :). No obstante, si la clase Vehiculo no tiene ese método, Coche no puede sobreescribirlo. Ese método, entonces, no existe para la clase Vehículo, así que el código no llega a compilar.

¿Y si haces al revés, y eliminas el método de la clase Coche? En ese caso, como puedes suponer, se ejecuta el método de la clase Vehiculo. Quizá te preguntes qué variable se imprime, en ese caso.

Se imprime, en ambos casos, la variable de la clase Vehiculo. Quizá te preguntes por qué. Se debe a que, aunque la clase Coche herede el método, este método sigue encontrándose en la clase Vehiculo, e imprime la variable de la clase Vehiculo.

¿Y si la clase Vehiculo no fuera abstracta?

El objeto de tipo superclase instanciado con una referencia de la superclase siempre va a acceder al código de su propia clase. En este caso, no se sobreescribe nada.

Como ves, el método numeroDeAsientos() se sobreescribe en el caso del último objeto. Esto se debe a que, aunque utiliza una referencia de tipo Vehiculo, superclase, se trata en realidad de un objeto de tipo Coche, subclase. En cambio, el primer objeto, de tipo Vehiculo y con una referencia de tipo Vehiculo, accede al método de la clase Vehiculo. En este caso, el método no se sobreescribe. La superclase, por sí sola, no sabe qué clases la extienden, ni utiliza sus métodos.

Ejercicios recomendados

  • ¿Qué ocurre si la clase Vehiculo posee un método estático llamado X y la clase Coche posee un método no estático con el mismo nombre y parámetros?
  • ¿Y si pasa al revés, y el método X no es estático en Vehiculo, pero sí lo es en Coche?

¡Espero tus respuestas en los comentarios!

Deja una respuesta 0 comentarios