La sobreescritura de métodos
La herencia permite ampliar la funcionalidad con nuevos métodos, pero también reescribir métodos heredados adaptándolos a las necesidades de la subclase. Por ejemplo, en Felino existe public void hacerRuido(), que se sobrescribe en Tigre con System.out.println(rugir()) y en Gato con System.out.println(maullar()).
Reglas para la sobreescritura
- Solo se pueden sobrescribir métodos heredados.
- El nuevo método mantiene el mismo prototipo (nombre, parámetros y tipo de retorno).
- La visibilidad puede modificarse hacia mayor accesibilidad (de
protectedapublic, por ejemplo).
La sobreescritura de propiedades
Las propiedades con el mismo nombre en la subclase ocultan a las heredadas. Este mecanismo no se suele usar con frecuencia.
Los constructores y la herencia
Los constructores no se heredan, pero al invocar un constructor de una subclase se ejecuta una cadena de llamadas hacia la superclase usando super:
super(); // Constructor sin parámetros de la superclase
super(lista de parámetros); // Constructor con parámetros de la superclase
No están permitidos encadenamientos como super.super.nace().
El compilador añade automáticamente super() como primera línea del constructor, lo que puede causar errores si el constructor padre requiere parámetros. La cadena de constructores asciende hasta Object, la superclase de todas las clases en Java.
¿Y tú de quién eres? El operador instanceof
Verifica si una referencia apunta a una instancia de una clase o cualquiera de sus superclases, devolviendo boolean:
Felino f = new Felino();
if (f instanceof Felino) // true
if (f instanceof Mamifero) // true
if (f instanceof Object) // true
Algunos métodos heredados de Object
public boolean equals(Object obj) — Compara el objeto actual con el parámetro. Se suele sobrescribir para comparar contenidos en lugar de referencias.
public String toString() — Devuelve una cadena describiendo el estado del objeto: "NombreClase@direcciónMemoriaHexadecimal". Se sobrescribe con frecuencia para mostrar información útil.
El modificador final
Previene la sobreescritura de un método:
final tipoRetorno nombreMetodo() { … }
Para evitar que una clase sea extendida:
final class NombreClase { … }
El modificador abstract
Una clase abstracta no permite crear objetos directamente, pero sí subclases:
abstract class NombreClase { … }
Los métodos abstractos se declaran sin implementación:
abstract tipoRetorno nombreMetodo();
Las subclases están obligadas a sobrescribir todos los métodos abstractos, o declararlos también abstractos. Las clases abstractas actúan como contratos que deben cumplir sus subclases. Son útiles para clases como Felino, Mamifero o Vehiculo, que conceptualmente no tiene sentido instanciar.
Los castings
Conversión de tipos primitivos
Sin pérdida de información: el tipo destino tiene mayor capacidad. El compilador convierte automáticamente.
Con pérdida de información: el tipo destino tiene menor capacidad. Requiere el operador de casting explícito:
float f = (float) 2 * 3.1415 * radio;
Conversiones de referencias a objetos
Upcasting (sin pérdida): conversión hacia la superclase, segura y automática.
Downcasting (con pérdida): conversión hacia la subclase, requiere el operador de casting. Se recomienda validar con instanceof antes:
if (f instanceof Tigre)
Tigre t2 = (Tigre) f;
Si la conversión no es posible, se lanza ClassCastException en tiempo de ejecución.
El polimorfismo
El polimorfismo es una característica derivada de la herencia y el casting. Un objeto puede usarse como su clase o como cualquiera de sus superclases:
Tigre ti = new Tigre();
Felino fe = ti;
fe.caza(); // Correcto
fe.hacerRuido(); // Ejecuta la versión de Tigre (sobreescritura)
fe.ruge(); // Error: ruge() no pertenece a Felino
Utilidades del polimorfismo:
- Generalización mediante un ancestro común.
- Almacenar objetos de distintas clases en una misma estructura de datos.
Ejemplo con método generalizado:
public void manejarFelinos(Felino f) {
f.caza();
f.hacerRuido();
}
Las interfaces de Java
Concepto
Una interfaz es una clase abstracta extrema donde:
- Todos los métodos son públicos y abstractos.
- No existen atributos ni constructores.
Las interfaces especifican las operaciones obligatorias para un conjunto de clases. Una clase implementa una interfaz (en lugar de heredar) usando implements. Los nombres de interfaces suelen expresar capacidades: Comparable, Clonable, CapazDeCorrer, CapazDeEscuchar.
Declaración de una interfaz
public interface CapazDeVolar {
void despegar();
void aterrizar();
void volar();
}
Implementación de una interfaz
public class Helicoptero implements CapazDeVolar {
// Debe dar código a todos los métodos de la interfaz
}
Una clase puede heredar de otra e implementar múltiples interfaces:
public class B extends A implements Inter1, Inter2 { … }
La herencia de interfaces
Las interfaces pueden heredar entre sí:
public interface CapazDeVolar extends CapazDeMoverse {
void despegar();
void aterrizar();
void volar();
}
Una clase que implemente la subinterfaz debe implementar los métodos de ambas.
Las interfaces y el polimorfismo
Un objeto puede apuntarse mediante una referencia a la interfaz:
Helicoptero h = new Helicoptero();
CapazDeVolar cdv = h;
h.arrancar();
cdv.despegar();
cdv.volar();
cdv.aterrizar();
cdv.parar(); // Error: parar() no pertenece a la interfaz
h.parar(); // Correcto
Utilidades de las interfaces
Las interfaces son una herramienta excelente para el diseño: permiten describir la funcionalidad de las clases de forma compacta, facilitan el trabajo paralelo de equipos y mejoran la comunicación entre programadores y diseñadores. Requieren práctica para familiarizarse, ya que son las estructuras más abstractas del lenguaje Java.