Hytale Modding
Conceptos básicos de Java

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 + "!");
    }
}
Terminología de Herencia
  • 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;
    }
}
Reglas del Constructor
  • 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!"
    }
}
@Anulación de anotación

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 partes
  • protected - Accesible en clase y subclases
  • private - 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);
}
Ventajas del polimorfismo

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

  1. Jerarquía de Vehículos: Crea una clase Vehicle con propiedades como velocidad y combustible. Crea las subclases Car y Motorcycle con sus propias características únicas.

  2. Calculadora de Formas: Crea una clase Shape con un método calculateArea(). Crea las subclases Circle, Rectangle y Triangle que sobrescriban este método.

  3. Personajes RPG: Crea una clase Character. Extiéndela para crear las clases Warrior, Mage y Archer con habilidades únicas.

  4. Sonidos de Animales: Crea una clase Animal con un método makeSound(). Crea varias subclases de animales que sobrescriban este método.