La herencia permite definir una clase a partir de otra y especializar su comportamiento.
En programación intentamos evitar la duplicación innecesaria, aunque la herencia no es la única forma de reutilizar código.
Imagina que estás programando un videojuego. Creas una clase Enemigo que tiene vida y posición. Luego creas un Jugador que tiene… vida y posición. Y luego un NPC que tiene… vida y posición.
¿Vas a escribir int vida; int x; int y; tres veces? ¿Y si luego quieres cambiar cómo funciona la posición? ¿Vas a editar tres archivos?
La herencia nos permite crear una clase base y hacer que otras clases derivadas reciban sus miembros accesibles.
El concepto “es-un” (is-a)
La herencia establece una relación jerárquica.
- Un Coche es un Vehículo.
- Un Gato es un Animal.
- Un Gerente es un Empleado.
Si no puedes decir la frase “es un”, probablemente no deberías usar herencia (quizás deberías usar composición, “tiene un”, pero eso es tema avanzado).
La palabra clave extends
En Java, para heredar usamos extends. Vamos a verlo con el clásico ejemplo de vehículos.
La superclase (padre)
Definimos lo que es común a todos.
// Archivo: Vehiculo.java
public class Vehiculo {
protected String marca; // protected: Las hijas pueden tocarlo
protected double velocidad;
public Vehiculo(String marca) {
this.marca = marca;
this.velocidad = 0;
}
public void acelerar() {
this.velocidad += 10;
System.out.println("Vehículo acelerando... " + this.velocidad + " km/h");
}
}
La subclase (hija)
Ahora creamos un Coche. El Coche hereda todo lo que tiene el Vehículo, y añade sus cosas propias (número de puertas).
// Archivo: Coche.java
public class Coche extends Vehiculo { // Coche hereda de Vehiculo
private int numeroPuertas;
public Coche(String marca, int puertas) {
super(marca); // Llamamos al constructor del padre (Ver abajo)
this.numeroPuertas = puertas;
}
// Método propio de Coche
public void ponerAireAcondicionado() {
System.out.println("Aire puesto. Qué fresquito.");
}
}
Al usar extends, la clase Coche automáticamente tiene marca, velocidad y el método acelerar(), sin tener que escribirlos.
Coche miCoche = new Coche("Toyota", 5);
miCoche.acelerar(); // ¡Funciona! Heredado de Vehiculo.
miCoche.ponerAireAcondicionado(); // Propio de Coche.
La palabra clave super
Cuando heredas, a veces necesitas llamar a “papá”. Para eso sirve super. Tiene dos usos principales:
En el constructor (super())
Cuando nace un hijo (Coche), primero tiene que nacer la parte de padre (Vehiculo).
Java te obliga a llamar al constructor de la superclase.
- Debe ser la primera línea del constructor del hijo.
- Si no lo pones, Java intentará poner un
super()invisible (llamando al constructor vacío del padre). Si el padre no tiene constructor vacío… ¡Error de compilación!
Accediendo a métodos ocultos
Si el hijo y el padre tienen un método con el mismo nombre (Sobrescritura, que veremos en el siguiente post), puedes usar super.metodo() para ejecutar la versión original del padre.
Java y la herencia simple
Aquí hay una diferencia gigante con C++. En Java, una clase solo puede tener UN padre.
- ✅
public class Coche extends Vehiculo - ❌
public class CocheAmfibio extends Barco, Coche(Error)
Esto se hizo para evitar el temido Problema del Diamante (cuando dos padres tienen un método igual, ¿cuál hereda el hijo?). Java prefiere la simplicidad. Si necesitas comportamientos de múltiples fuentes, Java usa Interfaces (lo veremos pronto).
La clase Object: el padre de todos
En Java, hay una verdad universal: Todo es un Objeto.
Si tú creas una clase que no extiende de nada:
public class Perro { ... }
Java, silenciosamente, hace esto por ti:
public class Perro extends Object { ... }
La clase java.lang.Object es la raíz de toda la jerarquía de Java. Esto significa que CUALQUIER objeto que crees tiene automáticamente los métodos definidos en Object.
Los métodos que todos heredamos
Gracias a heredar de Object, cualquier clase tiene:
toString(): Devuelve una representación textual. La implementación deObjectcombina el nombre de la clase con el hash en hexadecimal; no muestra una dirección de memoria.equals(Object obj): Compara si dos objetos son iguales. Por defecto compara referencias (==), pero solemos sobrescribirlo para comparar contenido (como IDs).hashCode(): Un número utilizado por colecciones hash comoHashMap. No tiene que ser único: objetos distintos pueden producir el mismo valor.getClass(): Nos dice de qué tipo es el objeto en tiempo de ejecución.