Herencia en Java — ejercicios con animales, figuras y la plantilla de un restaurante (y el cast que convirtió a mi gato en perro)

En la práctica de herencia tenía un array de Animal con perros y gatos, y quería imprimir la raza de los perros. getRaza() solo existe en Perro, así que hice lo que me pareció lógico: convertir cada animal en perro.

for (Animal a : refugio) {
    Perro p = (Perro) a;
    System.out.println(p.getRaza());
}

Compiló sin un aviso. El primer perro imprimió su raza y, al llegar al gato, el programa explotó con un ClassCastException. El compilador no se quejó porque **un Animal podría ser un Perro**; el problema es que ese animal en concreto no lo era. Un cast no transforma el objeto: solo le dice a Java «confía en mí». Y yo no tenía motivos para que confiara.

En estos ejercicios vas a construir jerarquías completas: clases abstractas, constructores con super, métodos sobrescritos que reutilizan los del padre y arrays polimórficos. Antes de las soluciones, tres fragmentos con trampa (el primero es justo mi cast). Si necesitas repasar antes de empezar, tienes la teoría y la práctica del tema.

Cómo usar este artículo

  1. Lee el enunciado y la salida esperada.
  2. Inténtalo al menos 15 minutos antes de abrir la pista.
  3. Antes de escribir código, dibuja la jerarquía: quién hereda de quién y qué método va en cada piso.
  4. Compara con la solución y lee el porqué de cada decisión.

Cada clase va en su propio archivo (Animal.java, Perro.java…) y un Main.java con las pruebas. Escribe @Override en todo lo que sobrescribas: te ahorrará más de un fragmento de la sección de errores.

Nivel 1 — Básico: animales del refugio

Enunciado. Crea una jerarquía para un refugio de animales:

  • Animal, abstracta, con nombre y edad (private final). Si la edad es negativa, se guarda 0.
  • Un método abstracto String sonido() y un método String presentarse() que devuelva Toby (3 años) dice ¡Guau!.
  • Perro extends Animal, con raza. Su sonido es ¡Guau! y su presentarse() añade al final y es un beagle, reutilizando el del padre.
  • Gato extends Animal, con un boolean casero. Si es casero dice Miau; si no, ¡Fffff!.
  • En main, un array Animal[] con los cuatro animales de la prueba: presenta a todos, cuenta los perros y calcula la edad media.

Pruebas y salida esperada:

Animal[] refugio = {
    new Perro("Toby", 3, "beagle"),
    new Gato("Luna", 2, true),
    new Gato("Sombra", -1, false),
    new Perro("Rex", 7, "pastor alemán")
};
Toby (3 años) dice ¡Guau! y es un beagle
Luna (2 años) dice Miau
Sombra (0 años) dice ¡Fffff!
Rex (7 años) dice ¡Guau! y es un pastor alemán
Perros: 2 · edad media: 3,0

El presentarse() del perro es super.presentarse() + " y es un " + raza. Para la edad media, ojo con la división entera: convierte la suma a double antes de dividir.

Nivel 2 — Intermedio: figuras que se comparan y se ordenan

Enunciado. Esta vez no buscamos la figura mayor, como en la práctica, sino ordenarlas todas:

  • Figura, abstracta, con double area() y String getNombre() abstractos.
  • Un método final boolean esMayorQue(Figura otra) que compare por área.
  • toString() con el formato Círculo r=1.5 de área 7,07.
  • Un método static void ordenar(Figura[] figuras) que las ordene de menor a mayor área usando solo esMayorQue (método de la burbuja).
  • Circulo (radio) y Triangulo (tres lados, área con la fórmula de Herón).
  • Triangulo tiene un método static boolean esValido(double a, double b, double c): los tres lados positivos y cada uno menor que la suma de los otros dos.

Pruebas y salida esperada:

System.out.println(Triangulo.esValido(3, 4, 5) + " " + Triangulo.esValido(1, 2, 10));

Figura[] figuras = {
    new Circulo(1.5),
    new Triangulo(3, 4, 5),
    new Circulo(1),
    new Triangulo(5, 5, 6)
};

System.out.println(figuras[0].esMayorQue(figuras[1]));
Figura.ordenar(figuras);
for (Figura f : figuras) {
    System.out.println(f);
}
true false
true
Círculo r=1.0 de área 3,14
Triángulo 3.0-4.0-5.0 de área 6,00
Círculo r=1.5 de área 7,07
Triángulo 5.0-5.0-6.0 de área 12,00

Fórmula de Herón: con el semiperímetro s = (a + b + c) / 2, el área es Math.sqrt(s * (s - a) * (s - b) * (s - c)). En la burbuja, si figuras[j].esMayorQue(figuras[j + 1]), intercámbialas con una variable auxiliar.

Nivel 3 — Desafío: la plantilla de un restaurante

Enunciado. Modela la plantilla del restaurante Casa Pepe. Cada hora trabajada cuesta 10 €:

  • Trabajador, abstracta, con nombre (private) y horas semanales (protected final, nunca negativas). Constante PRECIO_HORA = 10.0.
  • double costeSemanal() (horas × precio), String getPuesto() abstracto y toString() con el formato Cocinero Ana 500,00 €.
  • Cocinero: si hace turno de noche, su coste es un 25 % mayor.
  • Camarero: cuesta lo normal, pero acumula propinas (registrarPropina, ignorando cantidades negativas). Las propinas no son coste del restaurante.
  • Encargado extends Camarero: cuesta lo que un camarero más 120 € de plus de responsabilidad.
  • Plantilla, con un array de capacidad fija: contratar (falla si está llena o ya hay alguien con ese nombre, sin distinguir mayúsculas), costeTotal(), masCaro(), propinasSala() (las de todos los camareros, encargados incluidos) y listar().

Pruebas y salida esperada:

Plantilla casaPepe = new Plantilla(4);
Camarero lucia = new Camarero("Lucía", 30);
Encargado tomas = new Encargado("Tomás", 40);

System.out.println(casaPepe.contratar(new Cocinero("Ana", 40, true)));
casaPepe.contratar(lucia);
casaPepe.contratar(tomas);
System.out.println(casaPepe.contratar(new Cocinero("ana", 20, false)));   // repetida
casaPepe.contratar(new Cocinero("Raúl", 35, false));
System.out.println(casaPepe.contratar(new Camarero("Eva", 20)));         // llena

lucia.registrarPropina(35.5);
tomas.registrarPropina(20);
lucia.registrarPropina(-5);                                              // ignorada

casaPepe.listar();
System.out.printf("Coste semanal: %.2f €%n", casaPepe.costeTotal());
System.out.println("Más caro: " + casaPepe.masCaro().getNombre());
System.out.printf("Propinas de sala: %.2f €%n", casaPepe.propinasSala());
true
false
false
Cocinero  Ana     500,00 €
Camarero  Lucía   300,00 €
Encargado Tomás   520,00 €
Cocinero  Raúl    350,00 €
Coste semanal: 1670,00 €
Más caro: Tomás
Propinas de sala: 55,50 €

El costeSemanal() del cocinero y el del encargado empiezan por super.costeSemanal(). Para las propinas de sala, instanceof Camarero también es true para el encargado: no necesitas preguntar por los dos.

¿Qué falla aquí?

Tres fragmentos que compilan. Antes de leer la explicación, intenta predecir qué pasa.

Fragmento 1 — El cast de confianza

Animal[] refugio = { new Perro("Toby", 3, "beagle"), new Gato("Luna", 2, true) };
for (Animal a : refugio) {
    Perro p = (Perro) a;
    System.out.println(p.getRaza());
}

Imprime beagle y después lanza ClassCastException: class Gato cannot be cast to class Perro. Es mi historia del principio. El compilador acepta el cast porque algún Animal podría ser un Perro, pero quien decide al ejecutar es el objeto real, y Luna es una gata. La solución es preguntar antes:

for (Animal a : refugio) {
    if (a instanceof Perro p) {
        System.out.println(p.getRaza());
    }
}

«arreglar» el ClassCastException con un try-catch alrededor del cast. Ocultas el síntoma, pero el diseño sigue mal: si sabes que puede no ser un perro, pregúntalo con instanceof.

Fragmento 2 — El perro que no ladraba

class Animal {
    public String sonido() { return "..."; }
    public String hablar() { return "Dice " + sonido(); }
}

class Perro extends Animal {
    public String sonidos() { return "¡Guau!"; }   // sin @Override
}

Animal toby = new Perro();
System.out.println(toby.hablar());

Imprime Dice .... El perro no ha sobrescrito sonido(): ha creado un método nuevo, sonidos(), con una s de más. Para Java son dos métodos distintos, así que hablar() sigue llamando al sonido() de Animal. No hay error de compilación ni de ejecución: solo un resultado equivocado, que es lo peor.

Con @Override encima de sonidos(), el compilador lo habría cazado al momento: method does not override or implement a method from a supertype. Y si sonido() fuera abstracto en Animal, como en el nivel 1, tampoco compilaría, porque Perro no lo implementaría.

Fragmento 3 — El toString infinito

class Animal {
    protected String nombre = "Toby";

    @Override
    public String toString() { return nombre; }
}

class Perro extends Animal {
    @Override
    public String toString() {
        return toString() + " (perro)";
    }
}

System.out.println(new Perro());

Lanza StackOverflowError. La intención era «lo que imprima Animal más (perro)«, pero toString() sin nada delante llama al toString() de este mismo objeto, que es… este mismo método. Se llama a sí mismo una y otra vez hasta llenar la pila. La solución es una palabra:

return super.toString() + " (perro)";     // Toby (perro)

Cada vez que un método sobrescrito necesite «lo que hacía el padre», escribe super. delante. Sin él, estás llamando a tu propia versión.

Soluciones

Nivel 1 — Animales del refugio

public abstract class Animal {

    private final String nombre;
    private final int edad;

    public Animal(String nombre, int edad) {
        this.nombre = nombre;
        this.edad = Math.max(0, edad);                // nada de edades negativas
    }

    public String getNombre() { return nombre; }
    public int getEdad()      { return edad; }

    // Todo animal hace un sonido, pero cada uno el suyo
    public abstract String sonido();

    public String presentarse() {
        return nombre + " (" + edad + " años) dice " + sonido();
    }
}
public class Perro extends Animal {

    private final String raza;

    public Perro(String nombre, int edad, String raza) {
        super(nombre, edad);                          // primero la parte de Animal
        this.raza = raza;
    }

    public String getRaza() { return raza; }

    @Override
    public String sonido() { return "¡Guau!"; }

    @Override
    public String presentarse() {
        return super.presentarse() + " y es un " + raza;
    }
}
public class Gato extends Animal {

    private final boolean casero;

    public Gato(String nombre, int edad, boolean casero) {
        super(nombre, edad);
        this.casero = casero;
    }

    public boolean isCasero() { return casero; }

    @Override
    public String sonido() { return casero ? "Miau" : "¡Fffff!"; }
}
public class Main {
    public static void main(String[] args) {
        Animal[] refugio = {
            new Perro("Toby", 3, "beagle"),
            new Gato("Luna", 2, true),
            new Gato("Sombra", -1, false),
            new Perro("Rex", 7, "pastor alemán")
        };

        int perros = 0, sumaEdades = 0;
        for (Animal a : refugio) {
            System.out.println(a.presentarse());
            if (a instanceof Perro) perros++;
            sumaEdades += a.getEdad();
        }
        System.out.printf("Perros: %d · edad media: %.1f%n", perros, (double) sumaEdades / refugio.length);
    }
}

Por qué así:

  • **Animal es abstracta** porque en el refugio no hay «animales genéricos»: cada uno es perro o gato. Y sonido() es abstracto porque no hay un sonido que valga para todos.
  • **La validación de la edad vive en Animal.** Así sirve para perros, gatos y cualquier hija futura. Sombra se creó con -1 y sale con 0 años sin que Gato haga nada.
  • **presentarse() usa sonido() sin saber cuál será.** Cuando el objeto es un gato, se ejecuta el sonido() de Gato. El perro, además, sobrescribe presentarse() y reutiliza el del padre con super.
  • **(double) sumaEdades / refugio.length:** sin el cast, 12 / 4 sería división entera. Aquí daría 3 igualmente, pero con otras edades perderías los decimales.

Nivel 2 — Figuras que se comparan

public abstract class Figura {

    public abstract double area();
    public abstract String getNombre();

    // final: comparar por área es igual para todas las figuras
    public final boolean esMayorQue(Figura otra) {
        return area() > otra.area();
    }

    @Override
    public String toString() {
        return String.format("%s de área %.2f", getNombre(), area());
    }

    // Ordena de menor a mayor área (burbuja): solo usa esMayorQue
    public static void ordenar(Figura[] figuras) {
        for (int i = 0; i < figuras.length - 1; i++) {
            for (int j = 0; j < figuras.length - 1 - i; j++) {
                if (figuras[j].esMayorQue(figuras[j + 1])) {
                    Figura tmp = figuras[j];          // intercambiamos referencias,
                    figuras[j] = figuras[j + 1];      // no copiamos objetos
                    figuras[j + 1] = tmp;
                }
            }
        }
    }
}
public class Circulo extends Figura {

    private final double radio;

    public Circulo(double radio) {
        this.radio = Math.abs(radio);
    }

    @Override
    public double area() { return Math.PI * radio * radio; }

    @Override
    public String getNombre() { return "Círculo r=" + radio; }
}
public class Triangulo extends Figura {

    private final double a, b, c;

    public Triangulo(double a, double b, double c) {
        this.a = a;
        this.b = b;
        this.c = c;
    }

    // static: se puede comprobar ANTES de crear el triángulo
    public static boolean esValido(double a, double b, double c) {
        return a > 0 && b > 0 && c > 0 && a + b > c && a + c > b && b + c > a;
    }

    @Override
    public double area() {
        double s = (a + b + c) / 2;                   // semiperímetro
        return Math.sqrt(s * (s - a) * (s - b) * (s - c));   // fórmula de Herón
    }

    @Override
    public String getNombre() { return "Triángulo " + a + "-" + b + "-" + c; }
}

Por qué así:

  • **Figura no tiene constructor ni atributos**, y no pasa nada: Java le pone uno vacío, y el super() implícito de Circulo y Triangulo lo encuentra. Las hijas no necesitan escribir super.
  • **esMayorQue es final.** Si una hija pudiera redefinir qué significa «mayor», ordenar dejaría de ser fiable. Las hijas deciden cómo calcular su área, pero no cómo se comparan.
  • **ordenar es static y recibe Figura[].** No pertenece a ninguna figura concreta, y funciona con cualquier mezcla de círculos y triángulos (y con las figuras que añadas mañana) porque solo usa métodos de Figura.
  • En el intercambio se mueven referencias, no objetos. Los cuatro objetos siguen en el mismo sitio del montón; lo que cambia es qué posición del array apunta a cada uno.
  • **esValido es static porque se usa antes** de que exista el triángulo. Las excepciones, que son la forma de rechazar datos dentro del constructor, llegan en el siguiente tema.

El triángulo 3-4-5 es rectángulo, así que su área también sale de base × altura / 2 = 3 × 4 / 2 = 6. Herón da exactamente lo mismo: buena forma de comprobar que la fórmula está bien escrita.

Nivel 3 — La plantilla del restaurante

public abstract class Trabajador {

    public static final double PRECIO_HORA = 10.0;

    private final String nombre;
    protected final int horasSemana;                  // las hijas lo usan en sus cálculos

    public Trabajador(String nombre, int horasSemana) {
        this.nombre = nombre;
        this.horasSemana = Math.max(0, horasSemana);
    }

    public String getNombre() { return nombre; }

    public abstract String getPuesto();

    // Coste base: horas × precio. Las hijas lo amplían si hace falta
    public double costeSemanal() {
        return horasSemana * PRECIO_HORA;
    }

    @Override
    public String toString() {
        return String.format("%-9s %-6s %7.2f €", getPuesto(), nombre, costeSemanal());
    }
}
public class Cocinero extends Trabajador {

    private final boolean turnoNoche;

    public Cocinero(String nombre, int horasSemana, boolean turnoNoche) {
        super(nombre, horasSemana);
        this.turnoNoche = turnoNoche;
    }

    @Override
    public String getPuesto() { return "Cocinero"; }

    @Override
    public double costeSemanal() {
        double base = super.costeSemanal();           // reutilizamos el cálculo del padre
        return turnoNoche ? base * 1.25 : base;       // +25 % por nocturnidad
    }
}
public class Camarero extends Trabajador {

    private double propinas;                          // no es coste: lo pagan los clientes

    public Camarero(String nombre, int horasSemana) {
        super(nombre, horasSemana);
    }

    public void registrarPropina(double cantidad) {
        if (cantidad > 0) propinas += cantidad;
    }

    public double getPropinas() { return propinas; }

    @Override
    public String getPuesto() { return "Camarero"; }
    // costeSemanal(): el heredado de Trabajador sirve tal cual
}
public class Encargado extends Camarero {

    private static final double PLUS_RESPONSABILIDAD = 120.0;

    public Encargado(String nombre, int horasSemana) {
        super(nombre, horasSemana);
    }

    @Override
    public String getPuesto() { return "Encargado"; }

    @Override
    public double costeSemanal() {
        return super.costeSemanal() + PLUS_RESPONSABILIDAD;   // lo del camarero + el plus
    }
}
public class Plantilla {

    private final Trabajador[] trabajadores;
    private int numTrabajadores = 0;

    public Plantilla(int capacidad) {
        trabajadores = new Trabajador[capacidad];
    }

    public boolean contratar(Trabajador t) {
        if (t == null || numTrabajadores == trabajadores.length) return false;
        for (int i = 0; i < numTrabajadores; i++) {
            if (trabajadores[i].getNombre().equalsIgnoreCase(t.getNombre())) return false;
        }
        trabajadores[numTrabajadores++] = t;
        return true;
    }

    public double costeTotal() {
        double total = 0;
        for (int i = 0; i < numTrabajadores; i++) {
            total += trabajadores[i].costeSemanal();  // cada uno con su fórmula
        }
        return total;
    }

    public Trabajador masCaro() {
        if (numTrabajadores == 0) return null;
        Trabajador max = trabajadores[0];
        for (int i = 1; i < numTrabajadores; i++) {
            if (trabajadores[i].costeSemanal() > max.costeSemanal()) max = trabajadores[i];
        }
        return max;
    }

    // Solo los que están en sala (Camarero y sus hijas) tienen propinas
    public double propinasSala() {
        double total = 0;
        for (int i = 0; i < numTrabajadores; i++) {
            if (trabajadores[i] instanceof Camarero c) total += c.getPropinas();
        }
        return total;
    }

    public void listar() {
        for (int i = 0; i < numTrabajadores; i++) {
            System.out.println(trabajadores[i]);
        }
    }
}

Por qué así:

  • **costeSemanal() no es abstracto en Trabajador. Hay una fórmula por defecto (horas × 10 €) que sirve para el camarero tal cual. Solo la cambian quienes tienen algo distinto, y lo hacen ampliándola** con super.costeSemanal(), no copiándola.
  • **El encargado hereda de Camarero, no de Trabajador. Un encargado es un** camarero (está en sala y recibe propinas) con más responsabilidad. Así hereda registrarPropina y getPropinas sin escribir nada. Su super.costeSemanal() llama al de Camarero, que a su vez es el de Trabajador: 40 × 10 + 120 = 520 €.
  • **horasSemana es protected final.** Las hijas pueden leerlo si lo necesitan, pero nadie puede cambiarlo después de contratar. nombre es private porque las hijas no lo necesitan: tienen getNombre().
  • **propinasSala() usa instanceof Camarero**, y eso incluye a Tomás. Lucía tiene 35,50 € (la propina negativa se ignoró) y Tomás 20 €: 55,50 €.
  • **Plantilla solo conoce Trabajador.** Si mañana contratas un Repartidor, costeTotal, masCaro y listar funcionan sin tocarlos.

Escribir en Encargado el coste completo, horasSemana * PRECIO_HORA + 120. Hoy da lo mismo, pero si un día cambia cómo cobra un camarero, el encargado se quedará con la fórmula vieja. Con super.costeSemanal() hereda el cambio solo.

Míralo paso a paso en Python Tutor

Python Tutor ejecuta el código línea a línea y dibuja los objetos y las llamadas. Vamos a ver en directo el fragmento 2, el del perro que no ladraba. Elige Java, pega esto y avanza con Next:

public class Tutor {
    static class Animal {
        public String sonido() { return "..."; }
        public String hablar() { return "Dice " + sonido(); }
    }
    static class Perro extends Animal {
        public String sonidos() { return "¡Guau!"; }   // sin @Override
    }
    public static void main(String[] args) {
        Animal toby = new Perro();
        System.out.println(toby.hablar());
        Perro rex = new Perro();
        System.out.println(rex.sonidos());
    }
}

Salida:

Dice ...
¡Guau!

Qué observar:

  1. Tras new Perro(), el objeto del montón es de tipo Perro, aunque la variable toby sea de tipo Animal. El objeto real no cambia por cómo lo declares.
  2. Al ejecutar toby.hablar(), entra en el hablar() de Animal (el perro no tiene uno propio). Fíjate en que this apunta al objeto Perro.
  3. Dentro de hablar(), la llamada a sonido() busca primero en Perro… y no encuentra ningún sonido(), solo sonidos(). Así que salta al de Animal y devuelve "...". Ese salto es el error, en directo.
  4. Con rex.sonidos(), el método sí se ejecuta: existe, pero nadie lo usa desde hablar(). Ahora añade @Override encima de sonidos() en tu editor y mira el error que te da el compilador.

Chuletario: herencia en Java

Lo esencial en 30 segundos

  • class Hija extends Padre: la hija tiene todo lo del padre más lo suyo. Un solo padre por clase; si no pones ninguno, es Object.
  • Constructores: super(...) en la primera línea. Si no lo escribes, Java pone super() sin parámetros, y falla si el padre no tiene constructor vacío.
  • Sobrescribir = mismo método, otra versión (@Override siempre). Sobrecargar = mismo nombre, otros parámetros.
  • Polimorfismo: la variable decide qué puedes llamar; el objeto decide qué versión se ejecuta.
  • Cast hacia abajo: solo después de instanceof. abstract = «complétame»; final = «no me cambies».

Sintaxis

Quiero…CódigoResultado
Heredarpublic class Perro extends Animal { … }Perro tiene todo lo de Animal
Llamar al constructor del padresuper(nombre, edad); (primera línea)construye la parte heredada
Usar la versión del padresuper.presentarse()reutiliza y amplía
Sobrescribir@Override public String sonido() { … }sustituye la del padre
Sobrecargarpresentarse() y presentarse(String)conviven las dos
Atributo visible para las hijasprotected double saldo;hijas sí, resto no
Clase que no se instanciapublic abstract class Animalnew Animal() no compila
Método que las hijas deben escribirpublic abstract String sonido();sin cuerpo
Método que nadie puede cambiarpublic final boolean esMayorQue(…)no se sobrescribe
Clase de la que nadie heredapublic final class Xextends X no compila
Guardar hijas en el padreAnimal a = new Perro(…);polimorfismo
Array con varios tiposAnimal[] r = { new Perro(…), new Gato(…) };mismo bucle para todos
Comprobar y convertirif (a instanceof Perro p) { p.getRaza(); }cast seguro

Plantillas de los ejercicios

Hija que amplía un método del padre (Perro, Cocinero, Encargado):

@Override
public String presentarse() {
    return super.presentarse() + " y es un " + raza;
}

Toby → Toby (3 años) dice ¡Guau! y es un beagle

Método final que se apoya en uno abstracto (Figura):

public abstract double area();

public final boolean esMayorQue(Figura otra) {
    return area() > otra.area();
}

Ordenadas: 3,14 → 6,00 → 7,07 → 12,00

Recorrido polimórfico + instanceof (Plantilla):

for (int i = 0; i < numTrabajadores; i++) {
    total += trabajadores[i].costeSemanal();              // cada uno su fórmula
    if (trabajadores[i] instanceof Camarero c) propinas += c.getPropinas();
}

Coste → 1670,00 € · propinas de sala → 55,50 €

Errores que más se repiten

SíntomaCausa probable
constructor X in class X cannot be applied to given typesEl padre no tiene constructor vacío y no llamaste a super(...)
call to super must be first statement in constructorHay código antes de super(...)
X has private access in PadreEl atributo es private: usa el getter o hazlo protected
method does not override or implement a method from a supertypeNombre o parámetros distintos a los del padre
X is not abstract and does not override abstract method…La hija no implementa un método abstracto
cannot find symbol al llamar a un método de la hijaLa variable es del tipo padre: comprueba con instanceof
ClassCastExceptionCast hacia abajo sin comprobar el tipo real
StackOverflowError en un método sobrescritoFalta super. y el método se llama a sí mismo
Se ejecuta la versión del padre y no la tuyaNo sobrescribiste: hay un error de nombre o de parámetros (sin @Override)

Antes de entregar, pregúntate

  1. ¿Cada extends pasa la prueba del «es un»? Si es «tiene un», es composición, no herencia.
  2. ¿Todos los constructores de las hijas llaman a super(...) con los datos del padre en la primera línea?
  3. ¿He puesto @Override en todo lo que sobrescribo, y super. cuando amplío en vez de sustituir?
  4. ¿Cada cast hacia abajo va protegido por un instanceof? ¿Podría ser un método sobrescrito en su lugar?
Sigue aprendiendoTodos los artículos de Java

¿Te ha servido este artículo? Así puedes apoyar SergioLearns →

Sigue aprendiendo

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *