Herencia en Java
Introducción
La herencia es uno de los conceptos fundamentales de la programación orientada a objetos en Java. Este mecanismo permite que una clase adquiera las propiedades y comportamientos de otra clase, estableciendo una relación jerárquica entre ellas. La herencia es esencial para reutilizar código, crear jerarquías de clases y establecer relaciones del tipo "es un" entre objetos. En este artículo, exploraremos cómo implementar y aprovechar las ventajas de la herencia en Java.
El concepto de herencia
La herencia en Java se basa en la idea de que una clase (denominada clase hija o subclase) puede heredar atributos y métodos de otra clase (denominada clase padre, superclase o clase base). Esto permite crear una estructura jerárquica de clases donde las subclases heredan características comunes de su superclase.
Para establecer una relación de herencia en Java, se utiliza la palabra clave extends
:
public class ClasePadre {
// Atributos y métodos de la clase padre
}
public class ClaseHija extends ClasePadre {
// La clase hija hereda automáticamente los atributos y métodos
// de la clase padre, y puede añadir sus propios elementos
}
Ventajas de la herencia
- Reutilización de código: Evita duplicar código al heredar funcionalidades ya implementadas
- Extensibilidad: Permite añadir nuevas características a clases existentes
- Jerarquía de tipos: Establece una relación "es un" entre las clases
- Polimorfismo: Posibilita tratar objetos de diferentes subclases a través de referencias de su superclase
La clase Object
En Java, todas las clases heredan automáticamente de la clase Object
. Esta es la raíz de la jerarquía de clases en Java, y proporciona métodos comunes a todos los objetos como:
equals()
: Compara si dos objetos son igualestoString()
: Devuelve una representación en texto del objetohashCode()
: Genera un código hash para el objetogetClass()
: Devuelve el tipo de la clase en tiempo de ejecución
public class Ejemplo {
public static void main(String[] args) {
Ejemplo obj = new Ejemplo();
// Estos métodos están disponibles gracias a heredar de Object
System.out.println(obj.toString());
System.out.println(obj.getClass().getName());
}
}
Creando una jerarquía de clases
Veamos un ejemplo práctico de herencia con una jerarquía de vehículos:
// Clase base o superclase
public class Vehiculo {
private String marca;
private String modelo;
private int año;
public Vehiculo(String marca, String modelo, int año) {
this.marca = marca;
this.modelo = modelo;
this.año = año;
}
public void arrancar() {
System.out.println("El vehículo está arrancando");
}
public void detener() {
System.out.println("El vehículo se está deteniendo");
}
// Getters y setters
public String getMarca() {
return marca;
}
public String getModelo() {
return modelo;
}
public int getAño() {
return año;
}
}
// Subclase que hereda de Vehiculo
public class Coche extends Vehiculo {
private int numeroPuertas;
public Coche(String marca, String modelo, int año, int numeroPuertas) {
// Llamamos al constructor de la clase padre
super(marca, modelo, año);
this.numeroPuertas = numeroPuertas;
}
public void abrirMaletero() {
System.out.println("Abriendo maletero del coche");
}
public int getNumeroPuertas() {
return numeroPuertas;
}
}
// Otra subclase que hereda de Vehiculo
public class Motocicleta extends Vehiculo {
private boolean tieneCaballete;
public Motocicleta(String marca, String modelo, int año, boolean tieneCaballete) {
super(marca, modelo, año);
this.tieneCaballete = tieneCaballete;
}
public void hacerCaballito() {
System.out.println("¡La moto está haciendo un caballito!");
}
public boolean getTieneCaballete() {
return tieneCaballete;
}
}
La palabra clave super
La palabra clave super
se utiliza para acceder a elementos de la clase padre:
- Llamar al constructor de la superclase:
super(parámetros);
- Acceder a métodos de la superclase:
super.nombreMetodo();
- Acceder a atributos de la superclase:
super.nombreAtributo;
public class Ejemplo {
public static void main(String[] args) {
Coche miCoche = new Coche("Seat", "Ibiza", 2020, 5);
miCoche.arrancar(); // Método heredado de Vehiculo
miCoche.abrirMaletero(); // Método propio de Coche
System.out.println("Marca: " + miCoche.getMarca()); // Getter heredado
System.out.println("Puertas: " + miCoche.getNumeroPuertas()); // Getter propio
}
}
Sobrescritura de métodos
Una de las características más potentes de la herencia es la capacidad de sobrescribir (override) métodos de la clase padre en la clase hija. Esto permite que una subclase proporcione una implementación específica de un método ya definido en su superclase.
Para sobrescribir un método:
- La firma del método debe ser exactamente igual (nombre, parámetros y tipo de retorno)
- La visibilidad no puede ser más restrictiva que en la superclase
- Se recomienda usar la anotación
@Override
para indicar explícitamente la sobrescritura
public class Coche extends Vehiculo {
// ... código anterior ...
@Override
public void arrancar() {
System.out.println("El coche está arrancando el motor de combustión");
}
}
public class CocheElectrico extends Coche {
private int capacidadBateria;
public CocheElectrico(String marca, String modelo, int año, int numeroPuertas, int capacidadBateria) {
super(marca, modelo, año, numeroPuertas);
this.capacidadBateria = capacidadBateria;
}
@Override
public void arrancar() {
System.out.println("El coche eléctrico está encendiendo el motor eléctrico");
}
public int getCapacidadBateria() {
return capacidadBateria;
}
}
Herencia y constructores
Los constructores no se heredan, pero siempre se llama implícita o explícitamente al constructor de la superclase:
- Si no especificamos constructor en la subclase, Java llamará al constructor sin parámetros de la superclase
- Si queremos llamar a un constructor específico de la superclase, debemos usar
super()
como primera instrucción del constructor de la subclase
public class Ejemplo {
public static void main(String[] args) {
// Creamos objetos de diferentes tipos
Vehiculo vehiculo = new Vehiculo("Genérico", "Estándar", 2022);
Coche coche = new Coche("Toyota", "Corolla", 2021, 4);
CocheElectrico tesla = new CocheElectrico("Tesla", "Model 3", 2023, 4, 75);
// Probamos los métodos sobreescritos
System.out.println("Llamando a arrancar() en cada objeto:");
vehiculo.arrancar(); // Usa la versión de Vehiculo
coche.arrancar(); // Usa la versión sobreescrita en Coche
tesla.arrancar(); // Usa la versión sobreescrita en CocheElectrico
}
}
Limitaciones de la herencia en Java
En Java, hay algunas restricciones importantes sobre la herencia:
- Herencia simple: Una clase solo puede heredar directamente de una superclase (a diferencia de otros lenguajes que permiten herencia múltiple)
- Clases final: Una clase declarada como
final
no puede ser heredada - Métodos final: Un método declarado como
final
no puede ser sobrescrito en subclases
// Esta clase no puede ser heredada
final class ClaseFinal {
// Implementación...
}
class ClaseNormal {
// Este método no puede ser sobreescrito
final public void metodoFinal() {
// Implementación...
}
}
Herencia vs. Composición
Aunque la herencia es poderosa, no siempre es la mejor opción para reutilizar código. La composición (incluir objetos de otras clases como atributos) suele ser una alternativa más flexible:
// Enfoque de herencia
class CocheDeLujo extends Coche {
// CocheDeLujo es un Coche
}
// Enfoque de composición
class CocheDeLujo {
private Coche coche; // CocheDeLujo tiene un Coche
private SistemaEntretenimiento entretenimiento;
private AsientosCalefactables asientos;
// Implementación...
}
Regla general: Usa herencia cuando existe una relación "es un" verdadera, y composición cuando la relación es "tiene un".
Resumen
La herencia es un pilar fundamental de la programación orientada a objetos en Java que permite a las clases heredar atributos y comportamientos de otras clases. A través de la palabra clave extends
, podemos crear jerarquías de clases que comparten funcionalidades comunes, evitando así la duplicación de código. La sobrescritura de métodos nos permite especializar el comportamiento de las subclases, mientras que mecanismos como super
nos facilitan acceder a elementos de la clase padre. Es importante recordar que Java solo admite herencia simple y considerar la composición como alternativa cuando la relación entre objetos no es estrictamente del tipo "es un".