Introducción
La herencia te permite crear jerarquías de clases donde las clases hijas heredan atributos y métodos de una clase padre. La abstracción te ayuda a definir estructuras reutilizables.
Clases Abstractas
Una clase abstracta no puede ser instanciada directamente. Sirve como plantilla para otras clases.
// Clase abstracta base
public abstract class Animal {
protected String nombre;
public Animal(String nombre) {
this.nombre = nombre;
}
public void saludar() {
System.out.println("Hola, soy un animal llamado " + nombre);
}
// Método abstracto (obligatorio de implementar)
public abstract void hacerSonido();
}
// Clase concreta que hereda
public class Perro extends Animal {
public Perro(String nombre) {
super(nombre);
}
@Override
public void hacerSonido() {
System.out.println("¡Guau!");
}
}
// Otra clase concreta
public class Gato extends Animal {
public Gato(String nombre) {
super(nombre);
}
@Override
public void hacerSonido() {
System.out.println("¡Miau!");
}
}
// Uso en código
Animal perro = new Perro("Rex");
perro.saludar(); // Hola, soy un animal llamado Rex
perro.hacerSonido(); // ¡Guau!
Animal gato = new Gato("Michi");
gato.saludar(); // Hola, soy un animal llamado Michi
gato.hacerSonido(); // ¡Miau!
Polimorfismo
El polimorfismo te permite tratar objetos de diferentes tipos de manera uniforme a través de su clase base:
List<Animal> animales = List.of(
new Perro("Toby"),
new Gato("Luna")
);
for (Animal animal : animales) {
animal.saludar();
animal.hacerSonido();
}
Buenas Prácticas
- Usa herencia cuando existe una relación clara "es-un"
- Mantén las jerarquías de herencia simples (evita jerarquías profundas)
- Usa clases abstractas para definir comportamiento común
- Sobrescribe métodos apropiadamente con
@Override - Prefiere composición sobre herencia cuando sea apropiado
Malas Prácticas Comunes
- Crear jerarquías de herencia profundas (más de 3-4 niveles)
- Usar herencia solo para reutilizar código sin relación lógica
- No usar la anotación
@Override - Hacer todo abstracto innecesariamente
Ejemplo Práctico
En un juego, podrías tener diferentes tipos de personajes que comparten comportamiento común:
public abstract class Personaje {
protected int vida;
protected int nivel;
public abstract void atacar();
public void recibirDanio(int danio) {
vida -= danio;
}
}
public class Guerrero extends Personaje {
@Override
public void atacar() {
System.out.println("¡Guerrero ataca con espada!");
}
}
public class Mago extends Personaje {
@Override
public void atacar() {
System.out.println("¡Mago lanza un hechizo!");
}
}
Conclusión
La herencia y la abstracción son herramientas poderosas para crear estructuras de código reutilizables y organizadas. Usadas correctamente, te ayudan a modelar relaciones del mundo real y evitar duplicación de código.
En el próximo capítulo, aprenderemos sobre interfaces y cómo se diferencian de las clases abstractas.