← CC2008

Herencia

Semestre 02, 2026

Usos de la herencia

Reutilización de código: evita duplicación.
Modela jerarquías: Animal → Perro → Pastor Alemán.
Extensibilidad: nuevas clases a partir de otras.
Permite polimorfismo (muchas formas).

Modificadores de visibilidad

La herencia trae un modificador nuevo: protected, a medio camino entre private y public.

private: solo dentro de la misma clase.
protected: la clase y sus subclases.
public: cualquier clase del programa.
Sin modificador queda visible solo dentro del paquete.

Protected en la superclase


public class Persona {
    private String dpi;          // solo dentro de Persona
    protected String nombre;     // Persona y sus subclases
    public int edad;             // cualquier clase

    public void saludar() {
        System.out.println("Hola, soy " + nombre);
    }
}
Regla

Lo que la subclase necesite tocar directamente va protected; lo demás se queda private.

Extends

Una subclase hereda atributos y métodos de una superclase con extends.


public class Persona {
    protected String nombre;

    public void saludar() {
        System.out.println("Hola, soy " + nombre);
    }
}

public class Estudiante extends Persona {
    protected String carnet;
}

Jerarquía de clases

Toda clase hereda de Object. Ejemplo: Object → Persona → Estudiante.

toString(): representación en texto
equals(): comparación de igualdad
getClass(): tipo en ejecución
Facilita el polimorfismo

Override

Una subclase redefine un método heredado con la misma firma. Se resuelve en tiempo de ejecución.


class Estudiante extends Persona {
    @Override
    public void saludar() {
        System.out.println("Hola, soy estudiante");
    }
}

Super

Refiere a la superclase: invoca su constructor o sus métodos sobrescritos. Empezamos por la superclase.


public class Persona {
    protected String nombre;

    public Persona(String nombre) {
        this.nombre = nombre;
    }

    public void saludar() {
        System.out.println("Hola, soy " + nombre);
    }
}

Super en el constructor


public class Estudiante extends Persona {
    protected String carnet;

    public Estudiante(String nombre, String carnet) {
        super(nombre);
        this.carnet = carnet;
    }

    @Override
    public void saludar() {
        super.saludar();
        System.out.println("Estudio con el carnet " + carnet);
    }
}
super(nombre);

Va siempre en la primera línea del constructor. Persona se encarga del nombre y Estudiante solo agrega el carnet.

Super en un método sobrescrito


public class Estudiante extends Persona {
    protected String carnet;

    public Estudiante(String nombre, String carnet) {
        super(nombre);
        this.carnet = carnet;
    }

    @Override
    public void saludar() {
        super.saludar();
        System.out.println("Estudio con el carnet " + carnet);
    }
}
super.saludar();

Ejecuta el saludo de Persona y después agrega lo suyo. Sin esta línea, el saludo de la superclase se perdería.

Super en ejecución


public class Main {
    public static void main(String[] args) {
        Estudiante e = new Estudiante("Ana", "24001");
        e.saludar();
    }
}

Hola, soy Ana
Estudio con el carnet 24001
Orden

Primero corre la línea de Persona y después la de Estudiante, porque super.saludar() va antes del println.

Polimorfismo

Una referencia de la superclase puede apuntar a un objeto de cualquier subclase.


Persona p1 = new Estudiante();
Persona p2 = new Profesor();

p1.saludar(); // Hola, soy estudiante
p2.saludar(); // Buenos días, soy profesor

Upcasting y downcasting


Persona p = new Estudiante();     // upcasting implícito
Estudiante e = (Estudiante) p;    // downcasting explícito
Upcasting: seguro, siempre válido.
Downcasting: puede lanzar ClassCastException.

Arreglos y polimorfismo


ArrayList<Persona> personas = new ArrayList<>();
personas.add(new Estudiante());
personas.add(new Profesor());

for (Persona p : personas) {
    p.saludar(); // cada objeto ejecuta su versión
}

Clases abstractas

No se instancian; pueden tener métodos abstractos que las subclases deben implementar (un contrato).


abstract class Animal {
    public abstract void hacerSonido();
}

class Perro extends Animal {
    @Override
    public void hacerSonido() {
        System.out.println("Guau");
    }
}

Final

final class: no puede extenderse.
final en método: no puede sobrescribirse.

Ejemplo completo: la Pokédex

Vamos a construir un equipo Pokémon aplicando todo lo anterior, paso a paso.

Pikachu Squirtle Charizard

El problema sin herencia

Cada Pokémon como clase independiente repite los mismos datos una y otra vez.


public class Pikachu {
    String nombre;
    int nivel;
    int hp;
    public void mostrarFicha() { /* ... */ }
}

public class Squirtle {
    String nombre;   // otra vez
    int nivel;       // otra vez
    int hp;          // otra vez
    public void mostrarFicha() { /* ... */ }
}

Consecuencias de la duplicación

Un mismo atributo vive en 150 archivos distintos.
Agregar experiencia obliga a editar todas las clases.
No existe un tipo común que los agrupe en una lista.
La solución: una superclase Pokemon con lo que todos comparten.

La superclase Pokemon


public class Pokemon {
    protected String nombre;
    protected int nivel;
    protected int hp;

    public Pokemon(String nombre, int nivel, int hp) {
        this.nombre = nombre;
        this.nivel = nivel;
        this.hp = hp;
    }

    public void atacar() {
        System.out.println(nombre + " usa Placaje.");
    }

    public void mostrarFicha() {
        System.out.println(nombre + " | Nv. " + nivel + " | HP " + hp);
    }
}

La superclase Pokemon


public class Pokemon {
    protected String nombre;
    protected int nivel;
    protected int hp;

    public Pokemon(String nombre, int nivel, int hp) {
        this.nombre = nombre;
        this.nivel = nivel;
        this.hp = hp;
    }

    public void atacar() {
        System.out.println(nombre + " usa Placaje.");
    }

    public void mostrarFicha() {
        System.out.println(nombre + " | Nv. " + nivel + " | HP " + hp);
    }
}
protected String nombre;
protected int nivel;
protected int hp;

Lo que todo Pokémon tiene sin importar su tipo. Se declaran protected para que las subclases puedan usarlos directamente, sin abrirlos al resto del programa.

La superclase Pokemon


public class Pokemon {
    protected String nombre;
    protected int nivel;
    protected int hp;

    public Pokemon(String nombre, int nivel, int hp) {
        this.nombre = nombre;
        this.nivel = nivel;
        this.hp = hp;
    }

    public void atacar() {
        System.out.println(nombre + " usa Placaje.");
    }

    public void mostrarFicha() {
        System.out.println(nombre + " | Nv. " + nivel + " | HP " + hp);
    }
}
public Pokemon(String nombre,
int nivel, int hp) {

El constructor de la superclase inicializa los datos comunes. Las subclases lo van a reutilizar en vez de volver a escribirlo.

La superclase Pokemon


public class Pokemon {
    protected String nombre;
    protected int nivel;
    protected int hp;

    public Pokemon(String nombre, int nivel, int hp) {
        this.nombre = nombre;
        this.nivel = nivel;
        this.hp = hp;
    }

    public void atacar() {
        System.out.println(nombre + " usa Placaje.");
    }

    public void mostrarFicha() {
        System.out.println(nombre + " | Nv. " + nivel + " | HP " + hp);
    }
}
public void atacar() {
System.out.println(...);

Un ataque genérico que sirve para cualquier Pokémon. Cada tipo lo va a sobrescribir con su propio movimiento.

La superclase Pokemon


public class Pokemon {
    protected String nombre;
    protected int nivel;
    protected int hp;

    public Pokemon(String nombre, int nivel, int hp) {
        this.nombre = nombre;
        this.nivel = nivel;
        this.hp = hp;
    }

    public void atacar() {
        System.out.println(nombre + " usa Placaje.");
    }

    public void mostrarFicha() {
        System.out.println(nombre + " | Nv. " + nivel + " | HP " + hp);
    }
}
public void mostrarFicha() {

Este método se hereda tal cual y ninguna subclase necesita reescribirlo. Eso es reutilización de código.

PokemonElectrico


public class PokemonElectrico extends Pokemon {
    private int voltaje;

    public PokemonElectrico(String nombre, int nivel,
                            int hp, int voltaje) {
        super(nombre, nivel, hp);
        this.voltaje = voltaje;
    }

    @Override
    public void atacar() {
        System.out.println(nombre + " lanza " + voltaje + " V.");
    }
}
Pikachu

PokemonElectrico


public class PokemonElectrico extends Pokemon {
    private int voltaje;

    public PokemonElectrico(String nombre, int nivel,
                            int hp, int voltaje) {
        super(nombre, nivel, hp);
        this.voltaje = voltaje;
    }

    @Override
    public void atacar() {
        System.out.println(nombre + " lanza " + voltaje + " V.");
    }
}
extends Pokemon {
private int voltaje;

Hereda nombre, nivel y hp sin escribirlos otra vez, y agrega lo suyo. El voltaje solo tiene sentido para un Pokémon eléctrico, así que vive aquí.

PokemonElectrico


public class PokemonElectrico extends Pokemon {
    private int voltaje;

    public PokemonElectrico(String nombre, int nivel,
                            int hp, int voltaje) {
        super(nombre, nivel, hp);
        this.voltaje = voltaje;
    }

    @Override
    public void atacar() {
        System.out.println(nombre + " lanza " + voltaje + " V.");
    }
}
super(nombre, nivel, hp);

La primera línea del constructor delega en el constructor de Pokemon, que llena los tres atributos comunes. Después la subclase solo se encarga del voltaje.

PokemonElectrico


public class PokemonElectrico extends Pokemon {
    private int voltaje;

    public PokemonElectrico(String nombre, int nivel,
                            int hp, int voltaje) {
        super(nombre, nivel, hp);
        this.voltaje = voltaje;
    }

    @Override
    public void atacar() {
        System.out.println(nombre + " lanza " + voltaje + " V.");
    }
}
@Override
public void atacar() {

Misma firma que en la superclase, comportamiento distinto. Pikachu ya no usa Placaje, lanza su descarga eléctrica.

PokemonAgua


public class PokemonAgua extends Pokemon {
    private int presion;

    public PokemonAgua(String nombre, int nivel, int hp, int presion) {
        super(nombre, nivel, hp);
        this.presion = presion;
    }

    @Override
    public void atacar() {
        System.out.println(nombre + " ataca a " + presion + " psi.");
    }

    public void bucear() {
        System.out.println(nombre + " se sumerge y esquiva el ataque.");
    }
}
Squirtle

PokemonAgua


public class PokemonAgua extends Pokemon {
    private int presion;

    public PokemonAgua(String nombre, int nivel, int hp, int presion) {
        super(nombre, nivel, hp);
        this.presion = presion;
    }

    @Override
    public void atacar() {
        System.out.println(nombre + " ataca a " + presion + " psi.");
    }

    public void bucear() {
        System.out.println(nombre + " se sumerge y esquiva el ataque.");
    }
}
@Override
public void atacar() {

La misma estructura que el Pokémon eléctrico, con otro ataque. La superclase no cambió ni una línea para que esto funcione.

PokemonAgua


public class PokemonAgua extends Pokemon {
    private int presion;

    public PokemonAgua(String nombre, int nivel, int hp, int presion) {
        super(nombre, nivel, hp);
        this.presion = presion;
    }

    @Override
    public void atacar() {
        System.out.println(nombre + " ataca a " + presion + " psi.");
    }

    public void bucear() {
        System.out.println(nombre + " se sumerge y esquiva el ataque.");
    }
}
public void bucear() {

Un método que solo existe en esta subclase. Una variable declarada como Pokemon no lo puede invocar, aunque el objeto sea un PokemonAgua.

La jerarquía de la Pokédex

Un solo tipo general arriba, especializaciones abajo. Agregar un tipo nuevo no toca nada de lo anterior.

PokemonElectrico
Pikachu Jolteon Electabuzz
PokemonAgua
Squirtle Psyduck Vaporeon
PokemonFuego
Charmander Growlithe Flareon

Un equipo con polimorfismo


ArrayList<Pokemon> equipo = new ArrayList<>();

equipo.add(new PokemonElectrico("Pikachu", 12, 35, 90));
equipo.add(new PokemonAgua("Squirtle", 10, 44, 60));
equipo.add(new PokemonElectrico("Jolteon", 30, 65, 400));

for (Pokemon p : equipo) {
    p.mostrarFicha();
    p.atacar();
}

Un equipo con polimorfismo


ArrayList<Pokemon> equipo = new ArrayList<>();

equipo.add(new PokemonElectrico("Pikachu", 12, 35, 90));
equipo.add(new PokemonAgua("Squirtle", 10, 44, 60));
equipo.add(new PokemonElectrico("Jolteon", 30, 65, 400));

for (Pokemon p : equipo) {
    p.mostrarFicha();
    p.atacar();
}
ArrayList<Pokemon> equipo

La lista es de Pokemon, pero guarda objetos de cualquier subclase. Eso es upcasting, y ocurre solo.

Un equipo con polimorfismo


ArrayList<Pokemon> equipo = new ArrayList<>();

equipo.add(new PokemonElectrico("Pikachu", 12, 35, 90));
equipo.add(new PokemonAgua("Squirtle", 10, 44, 60));
equipo.add(new PokemonElectrico("Jolteon", 30, 65, 400));

for (Pokemon p : equipo) {
    p.mostrarFicha();
    p.atacar();
}
p.atacar();

Una sola llamada, tres comportamientos distintos. Java decide en tiempo de ejecución cuál versión de atacar corresponde a cada objeto.

Salida del combate


Pikachu | Nv. 12 | HP 35
Pikachu lanza 90 V.
Squirtle | Nv. 10 | HP 44
Squirtle ataca a 60 psi.
Jolteon | Nv. 30 | HP 65
Jolteon lanza 400 V.
Observen

mostrarFicha se heredó sin cambios y atacar cambió en cada subclase.

Pokemon como clase abstracta

Un Pokémon genérico no existe en el juego. Nadie debería poder crear uno.


public abstract class Pokemon {
    protected String nombre;
    protected int nivel;
    protected int hp;

    public Pokemon(String nombre, int nivel, int hp) { /* ... */ }

    public abstract void atacar();   // cada tipo define el suyo

    public void mostrarFicha() { /* se hereda tal cual */ }
}

Lo que gana el diseño

new Pokemon(...) deja de compilar.
Toda subclase queda obligada a definir atacar.
mostrarFicha se sigue heredando con código real.
El polimorfismo del equipo funciona igual.

El límite de la herencia

Gengar es de fantasma y veneno. Dragonite es de dragón y volador. ¿De qué clase heredan?

Gengar

fantasma + veneno

Dragonite

dragón + volador

Herencia múltiple no existe en Java


// no compila: Java no permite heredar de dos clases
class Gengar extends PokemonFantasma, PokemonVeneno { }
Salida forzada

Copiar el método del veneno en cada clase que lo necesite, y volvemos a la duplicación del inicio.

Salida correcta

Separar el tipo (herencia) de la habilidad (contrato), y la habilidad se declara en una interfaz.

Los tipos como interfaces

Cada tipo pasa a ser un contrato independiente, no una clase padre.


interface IFantasma {
    void atravesarMuros();
}

interface IVeneno {
    void envenenar();
}

Gengar implementa los dos contratos


class Gengar extends Pokemon implements IFantasma, IVeneno {
    @Override
    public void atravesarMuros() {
        System.out.println(nombre + " atraviesa el muro.");
    }

    @Override
    public void envenenar() {
        System.out.println(nombre + " envenena al rival.");
    }
}

La regla que queda

Una sola superclase

Gengar hereda de Pokemon, que es lo que todo Pokémon comparte.

Todas las interfaces

Los tipos se suman con implements, y se pueden combinar sin límite.

Próxima lección

Dragonite resuelve igual con IDragon e IVolador sobre la misma clase Pokemon.

Buenas prácticas y límites

Usa herencia solo si existe relación is-a.
Prefiere composición para "tiene-un".
Evita jerarquías profundas y complejas.
Java no tiene herencia múltiple de clases.

Resumen

extends reutiliza los atributos y métodos de la superclase.
super(...) llama al constructor de la superclase.
@Override especializa un comportamiento heredado.
Una lista de Pokemon ejecuta el ataque de cada subclase.
La clase abstracta impone un contrato sin poder instanciarse.
Para combinar habilidades hacen falta interfaces.