13 - Herencia
Aprende cómo crear jerarquías de clases y reutilizar el código de manera efectiva.
La herencia te permite crear nuevas clases basadas en las existentes. La nueva clase hereda todas las propiedades y métodos de la clase padre, y puede añadir sus propias o modificar las heredadas.
¿Qué es la herencia?
Piense en la herencia como un árbol genealógico. Un hijo hereda rasgos de su padre, pero también puede tener sus propios rasgos únicos.
// Clase padre (superclase)
public class Entity {
protected String name;
protected int health;
public Entity(String name, int health) {
this.name = name;
this.health = health;
}
public void takeDamage(int damage) {
health -= damage;
System.out.println(name + " took " + damage + " damage!");
}
}
// Clase hija (subclase)
public class Player extends Entity {
private int level;
public Player(String name, int health, int level) {
super(name, health); // Call parent constructor
this.level = level;
}
public void levelUp() {
level++;
System.out.println(name + " leveled up to " + level + "!");
}
}- Superclase/Padre: La clase de la que se está heredando (Entidad)
- Subclase/Hija: La clase heredada (Player)
- extends: Palabra clave para heredar de una clase
- super: palabra clave para acceder a los miembros de la clase padre
public class Monster extends Entity {
// Monster IS-A Entity
// Monster hereda de Entity
// Entity es el padre, Monstruo es el hijo
}La palabra clave extendida
Usa extends para heredar de una clase:
public class Animal {
protected String name;
public void makeSound() {
System.out.println(name + " hace un sonido");
}
}
public class Dog extends Animal {
public void wagTail() {
System.out.println(name + " mueve la cola");
}
}
public class Main {
public static void main(String[] args) {
Dog dog = new Dog();
dog.name = "Buddy";
dog.makeSound(); // Heredado de Animal
dog.wagTail(); // Método propio de Dog
}
}La palabra clave super
super se refiere a la clase padre:
Llamando al Constructor Padre
public class Entity {
protected String name;
public Entity(String name) {
this.name = name;
}
}
public class Player extends Entity {
private int level;
public Player(String name, int level) {
super(name); // Llamar al constructor de la clase padre PRIMERO
this.level = level;
}
}super()Debe ser la primera instrucción en el constructor del hijo.- Si no llama a
super(), Java automáticamente llama al constructor padre sin argumento - Si el padre no tiene un constructor sin argumentos, DEBE llamar a
super()con argumentos
// Incorrecto - super() no es el primero
public Player(String name, int level) {
this.level = level;
super(name); // Error !
}
// Correcto
public Player(String name, int level) {
super(name); // Primera instrucción
this.level = level;
}Llamando a Métodos Padres
public class Entity {
protected int health;
public void takeDamage(int damage) {
health -= damage;
System.out.println("¡La entidad ha sufrido daños!");
}
}
public class Player extends Entity {
@Override
public void takeDamage(int damage) {
super.takeDamage(damage); // Llamar a la versión principal
if (health < 20) {
System.out.println("Advertencia: ¡salud baja!");
}
}
}Sobrescritura de métodos
Las clases hijas pueden sustituir a los métodos de las clases padre:
public class Entity {
public void attack() {
System.out.println("¡La entidad está atacando!");
}
}
public class Player extends Entity {
@Override // Es una buena práctica utilizar esta anotación.
public void attack() {
System.out.println("¡El jugador balancea su espada!");
}
}
public class Monster extends Entity {
@Override
public void attack() {
System.out.println("¡El monstruo muerde!");
}
}
public class Main {
public static void main(String[] args) {
Player player = new Player();
Monster monster = new Monster();
player.attack(); // "¡El jugador balancea su espada!"
monster.attack(); // "¡El monstruo muerde!"
}
}Usa siempre @Override cuando se sobreescriban métodos:
- Ayuda a capturar errores tipográficos (si el método no existe en el padre, obtienes un error)
- Hace el código más claro
- Buena documentación
// Sin @Override - El error tipográfico no siempre se nota.
public void attac() { // Error tipográfico. Crea un nuevo método en lugar de reemplazarlo.
// ...
}
// Con @Override - Error detectado inmediatamente
@Override
public void attac() { // Error: El método no existe en el padre
// ...
}Modificadores de acceso en herencia
public- Accesible en todas partesprotected- Accesible en clase y subclasesprivate- Solo en la clase (no heredado)
public class Parent {
public int publicVar; // El hijo puede acceder
protected int protectedVar; // El hijo puede acceder
private int privateVar; // El hijo no puede acceder
private void privateMethod() {
// El hijo no puede llamar
}
protected void protectedMethod() {
// El hijo puede llamar
}
}
public class Child extends Parent {
public void test() {
publicVar = 10; // OK
protectedVar = 20; // OK
privateVar = 30; // ¡Error!
protectedMethod(); // OK
privateMethod(); // ¡Error!
}
}Ejemplos prácticos
Jerarquía de entidades del juego
// Clase base para todas las entidades
public class Entity {
protected String name;
protected int health;
protected int maxHealth;
protected double x, y, z;
public Entity(String name, int maxHealth) {
this.name = name;
this.health = maxHealth;
this.maxHealth = maxHealth;
}
public void takeDamage(int damage) {
health -= damage;
if (health < 0) health = 0;
System.out.println(name + " a recibido " + damage + " Daño. Vida:" + health);
}
public boolean isAlive() {
return health > 0;
}
public void moveTo(double x, double y, double z) {
this.x = x;
this.y = y;
this.z = z;
System.out.println(name + " desplazado a (" + x + ", " + y + ", " + z + ")");
}
}
// El jugador hereda de la entidad
public class Player extends Entity {
private int level;
private int experience;
private int mana;
public Player(String name) {
super(name, 100);
this.level = 1;
this.experience = 0;
this.mana = 50;
}
public void gainExperience(int amount) {
experience += amount;
System.out.println("Ganaste " + amount + " XP");
if (experience >= level * 100) {
levelUp();
}
}
private void levelUp() {
level++;
maxHealth += 10;
health = maxHealth;
mana += 5;
System.out.println("¡Has subido de Nivel! Ahora nivel " + level);
}
@Override
public void takeDamage(int damage) {
super.takeDamage(damage);
if (health < maxHealth * 0.25) {
System.out.println("⚠ ADVERTENCIA: ¡Salud baja!");
}
}
}
// Monstruo hereda de Entidad
public class Monster extends Entity {
private int attackPower;
private String type;
public Monster(String name, int health, int attackPower, String type) {
super(name, health);
this.attackPower = attackPower;
this.type = type;
}
public int attack() {
System.out.println(name + " ataca!");
return attackPower;
}
@Override
public void takeDamage(int damage) {
super.takeDamage(damage);
if (!isAlive()) {
System.out.println(name + " Ha sido derrotado!");
}
}
}
// El jefe hereda del monstruo (herencia multinivel)
public class Boss extends Monster {
private int phase;
public Boss(String name, int health, int attackPower) {
super(name, health, attackPower, "Jefe");
this.phase = 1;
}
@Override
public void takeDamage(int damage) {
super.takeDamage(damage);
// Cambio de fase al 50% de la vida
if (phase == 1 && health < maxHealth / 2) {
phase = 2;
System.out.println(name + " Entra en la FASE 2!");
}
}
@Override
public int attack() {
int damage = super.attack();
if (phase == 2) {
damage *= 2;
System.out.println("¡ATAQUE FEROZ!");
}
return damage;
}
}Jerarquía de elemento
// Clase base de Objeto
public class Item {
protected String name;
protected int value;
protected double weight;
public Item(String name, int value, double weight) {
this.name = name;
this.value = value;
this.weight = weight;
}
public void use() {
System.out.println("Usando " + name);
}
public String getInfo() {
return name + " ($" + value + ", " + weight + " kg)";
}
}
// Arma hereda de Objeto
public class Weapon extends Item {
private int damage;
private int durability;
public Weapon(String name, int value, double weight, int damage, int durability) {
super(name, value, weight);
this.damage = damage;
this.durability = durability;
}
@Override
public void use() {
if (durability > 0) {
System.out.println("Atacando con " + name + " causando " + damage + " de daño!");
durability--;
} else {
System.out.println(name + " está roto!");
}
}
@Override
public String getInfo() {
return super.getInfo() + ", Daño: " + damage + ", Durabilidad: " + durability;
}
}
// Consumible heredado de Objeto
public class Consumable extends Item {
private int healAmount;
private int uses;
public Consumable(String name, int value, double weight, int healAmount, int uses) {
super(name, value, weight);
this.healAmount = healAmount;
this.uses = uses;
}
@Override
public void use() {
if (uses > 0) {
System.out.println("Usado " + name + ", restauró " + healAmount + " de salud!");
uses--;
} else {
System.out.println("¡No queda " + name + "!");
}
}
@Override
public String getInfo() {
return super.getInfo() + ", Restaura: " + healAmount + ", Usos: " + uses;
}
}
// Armadura hereda de Objeto
public class Armor extends Item {
private int defense;
private String slot;
public Armor(String name, int value, double weight, int defense, String slot) {
super(name, value, weight);
this.defense = defense;
this.slot = slot;
}
@Override
public void use() {
System.out.println("Equipado " + name + " (+" + defense + " defensa)");
}
@Override
public String getInfo() {
return super.getInfo() + ", Defensa: " + defense + ", Ranura: " + slot;
}
}Polimorfismo
Los objetos secundarios pueden tratarse como objetos principales:
Entity entity1 = new Player("Alicia");
Entity entity2 = new Monster("Goblin", 50, 10, "Hostil");
Entity entity3 = new Boss("Dragón", 500, 50);
// Todos pueden usar los métodos de Entity
entity1.takeDamage(10);
entity2.takeDamage(10);
entity3.takeDamage(10);
// Arreglo de diferentes tipos
Entity[] entities = {
new Player("Bob"),
new Monster("Zombi", 30, 8, "Hostil"),
new Monster("Araña", 20, 5, "Hostil")
};
// Procesar todas las entidades de la misma manera
for (Entity entity : entities) {
entity.takeDamage(5);
}El polimorfismo te permite escribir código que funciona con tipos padre pero maneja correctamente los tipos hijo:
public void damageEntity(Entity entity, int damage) {
entity.takeDamage(damage);
// Funciona para Player, Monster, Boss, etc.
// Cada uno usa su propia versión de takeDamage()
}
// Se puede llamar con cualquier tipo de Entity
damageEntity(new Player("Alicia"), 10);
damageEntity(new Monster("Goblin", 50, 10, "Hostil"), 10);
damageEntity(new Boss("Dragón", 500, 50), 10);La clase de objeto
Todas las clases en Java heredan automáticamente de Object:
public class MyClass {
// Hereda automáticamente de Object
// Tiene métodos como toString(), equals(), etc.
}Métodos comunes de objetos que se deben sobrescribir:
public class Player {
private String name;
private int level;
@Override
public String toString() {
return "Jugador: " + name + " (Nv. " + level + ")";
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Player) {
Player other = (Player) obj;
return this.name.equals(other.name);
}
return false;
}
}Final Classes and Methods
final evita la herencia o la sobrescritura:
// Clase final – no puede ser extendida
public final class SpecialItem {
// Ninguna clase puede extender esta clase
}
// Método final – no puede ser sobrescrito
public class Entity {
public final void printName() {
System.out.println(name);
}
}
public class Player extends Entity {
@Override
public void printName() { // ¡Error! El método es final
// No se puede sobrescribir
}
}Errores comunes
```java
// Incorrecto – Olvidar llamar a super()
public class Player extends Entity {
public Player(String name) {
// ¡Error! Entity no tiene un constructor sin argumentos
}
}
// Correcto
public class Player extends Entity {
public Player(String name) {
super(name, 100); // Llama al constructor de la clase padre
}
}
// Incorrecto – Acceder a miembros privados
public class Child extends Parent {
public void test() {
privateVar = 10; // ¡Error! private no se hereda
}
}
// Correcto – Usar protected
public class Parent {
protected int protectedVar; // La clase hija puede acceder
}
// Incorrecto – Herencia múltiple (no permitida en Java)
public class Child extends Parent1, Parent2 { // ¡Error!
}
// Correcto – Solo herencia simple
public class Child extends Parent {
}
```Ejercicios prácticos
-
Jerarquía de Vehículos: Crea una clase
Vehiclecon propiedades como velocidad y combustible. Crea las subclasesCaryMotorcyclecon sus propias características únicas. -
Calculadora de Formas: Crea una clase
Shapecon un métodocalculateArea(). Crea las subclasesCircle,RectangleyTriangleque sobrescriban este método. -
Personajes RPG: Crea una clase
Character. Extiéndela para crear las clasesWarrior,MageyArchercon habilidades únicas. -
Sonidos de Animales: Crea una clase
Animalcon un métodomakeSound(). Crea varias subclases de animales que sobrescriban este método.