Herencia, super y overriding
La composición modela relaciones «tiene un». La herencia sirve para otra idea: una clase más específica es un tipo de otra clase y puede cumplir su mismo contrato.
De Persona a Alumno
Un alumno es una persona. Ambas comparten nombre, pero el alumno añade un curso:
class Persona {
private String nombre;
public Persona(String nombre) {
this.nombre = nombre;
}
public String presentacion() {
return "Soy " + nombre;
}
}
class Alumno extends Persona {
private String curso;
public Alumno(String nombre, String curso) {
super(nombre);
this.curso = curso;
}
}
extends Persona establece la relación. Alumno hereda los miembros accesibles de Persona, pero no accede directamente a sus atributos private.
Construir primero la parte padre
super(nombre) llama al constructor de la clase padre y debe ser la primera instrucción del constructor. Después se inicializa la parte específica del alumno.
No estamos creando dos objetos: existe un solo Alumno, construido con estado y comportamiento de ambos niveles.
Sobrescribir comportamiento
El alumno puede adaptar un método heredado manteniendo su firma:
class Alumno extends Persona {
private String curso;
public Alumno(String nombre, String curso) {
super(nombre);
this.curso = curso;
}
@Override
public String presentacion() {
return super.presentacion() + " y estudio " + curso;
}
}
@Overridepide al compilador que compruebe que realmente sobrescribimos un método.super.presentacion()reutiliza la versión del padre.- La versión del hijo completa el comportamiento.
Si escribimos mal el nombre o cambiamos los parámetros, @Override evita que el error pase inadvertido.
Sobrecarga y sobrescritura no son lo mismo
Sobrecargar (overload) significa mantener el nombre y cambiar los parámetros, normalmente dentro de una clase. Sobrescribir (override) significa que la clase hija redefine un método heredado con la misma firma.
// Sobrecarga
public String presentacion(String saludo) {
return saludo + ", " + presentacion();
}
La anotación @Override solo corresponde a la sobrescritura.
Ejemplo completo
class Persona {
private String nombre;
public Persona(String nombre) {
this.nombre = nombre;
}
public String presentacion() {
return "Soy " + nombre;
}
}
class Alumno extends Persona {
private String curso;
public Alumno(String nombre, String curso) {
super(nombre);
this.curso = curso;
}
@Override
public String presentacion() {
return super.presentacion() + " y estudio " + curso;
}
}
public class HerenciaDemo {
public static void main(String[] args) {
Persona persona = new Persona("Ada");
Alumno alumno = new Alumno("Leo", "Java 2");
System.out.println(persona.presentacion());
System.out.println(alumno.presentacion());
}
}
Cuándo no usar herencia
Una clase Coche no debería extender Motor: un coche tiene un motor, no es un motor.
class Motor {
public String arrancar() {
return "Motor arrancado";
}
}
class Coche {
private Motor motor;
public Coche(Motor motor) {
this.motor = motor;
}
public String arrancar() {
return motor.arrancar();
}
}
Aquí la composición refleja el modelo y permite cambiar el motor sin fingir una relación de tipos. Heredar solo para ahorrar unas líneas suele crear una relación difícil de sostener.
Límites saludables
- Mantén jerarquías pequeñas y fáciles de explicar.
- Comprueba que cualquier objeto hijo pueda usarse razonablemente como uno del padre.
- Conserva privados los detalles internos del padre.
- Prefiere composición cuando una pieza solo usa o contiene a otra.
Errores habituales
- Olvidar
super(...)cuando el padre no tiene constructor sin parámetros. - Intentar acceder desde el hijo a atributos
privatedel padre. - Confundir sobrecarga con sobrescritura.
- Quitar
@Overridepara ocultar un error de firma. - Crear herencia entre clases que solo colaboran.
Practica: empleado y responsable
Enunciado
Crea Empleado con nombre y método descripcion(). Crea Responsable, que es un empleado y añade el nombre de su equipo. Sobrescribe descripcion() reutilizando la del padre.
Criterios
- Los atributos son privados.
- El constructor hijo llama a
super. - La sobrescritura lleva
@Override. - No se duplican el nombre ni su inicialización.
Solución
class Empleado {
private String nombre;
public Empleado(String nombre) {
this.nombre = nombre;
}
public String descripcion() {
return "Empleado: " + nombre;
}
}
class Responsable extends Empleado {
private String equipo;
public Responsable(String nombre, String equipo) {
super(nombre);
this.equipo = equipo;
}
@Override
public String descripcion() {
return super.descripcion() + " | equipo: " + equipo;
}
}
public class EmpleadosDemo {
public static void main(String[] args) {
Empleado empleado = new Empleado("Nuria");
Responsable responsable = new Responsable("María", "Backend");
System.out.println(empleado.descripcion());
System.out.println(responsable.descripcion());
}
}
La herencia es válida porque un responsable sigue siendo un empleado. En la siguiente unidad aprovecharemos esa relación para trabajar con distintos subtipos mediante una referencia común.