Día 3 de 30 Fase 1: Java Vanilla — El Núcleo del Lenguaje (17 & 21)

Programación Orientada a Objetos de Verdad: Herencia, Interfaces y Polimorfismo

Clases abstractas y jerarquías sealed, interfaces con métodos default, sobrescritura y enlace dinámico, static y final, y enums con comportamiento

Para dominar. Al terminar deberías poder hacerlo sin ayuda.

  • 4h de estudio
  • 5 conceptos
  • 3 preguntas
  • 2 katas
Java 17 LTSJava 21 LTSHerenciaInterfacesPolimorfismoEnums
Al terminar este día sabrás 4
  • Diseñar una jerarquía con clases abstractas o sealed y justificar cuándo preferir composición.
  • Usar interfaces con métodos default, static y private sin convertirlas en clases.
  • Distinguir sobrecarga de sobrescritura y predecir qué método se ejecuta.
  • Explicar el orden de inicialización y modelar estados con enums con comportamiento.

Herencia, Clases Abstractas y Jerarquías Sealed

Una subclase hereda campos y métodos de su superclase con extends (solo de una). Una clase abstract no se puede instanciar y puede declarar métodos sin cuerpo que las subclases deben implementar. Con sealed ... permits decides exactamente qué clases pueden heredar.

En una línea public abstract sealed class Pago permits PagoTarjeta, PagoBizum { abstract BigDecimal comision(); }

Jerarquía sealed con clase abstracta, y composición java
public abstract sealed class Pago permits PagoTarjeta, PagoBizum {
    protected final BigDecimal importe;

    protected Pago(BigDecimal importe) {
        if (importe.signum() <= 0) throw new IllegalArgumentException("importe <= 0");
        this.importe = importe;
    }

    public abstract BigDecimal comision();            // cada tipo la calcula a su manera

    public BigDecimal total() {                        // comportamiento común heredado
        return importe.add(comision());
    }
}

public final class PagoTarjeta extends Pago {
    public PagoTarjeta(BigDecimal importe) { super(importe); }
    @Override public BigDecimal comision() { return importe.multiply(new BigDecimal("0.015")); }
}

public final class PagoBizum extends Pago {
    public PagoBizum(BigDecimal importe) { super(importe); }
    @Override public BigDecimal comision() { return BigDecimal.ZERO; }
}

// Composición en lugar de herencia: el servicio TIENE un notificador
public class ServicioCobros {
    private final Notificador notificador;
    public ServicioCobros(Notificador notificador) { this.notificador = notificador; }
}
Cómo funciona por dentro

Con extends, la subclase hereda los miembros no privados de la superclase y puede añadir o redefinir comportamiento. El constructor de la subclase llama siempre primero a un constructor de la madre (super(...)), de forma explícita o implícita. Una clase abstract sirve de plantilla: no se instancia, puede tener estado y métodos concretos, y obliga a las hijas a implementar sus métodos abstract.

El riesgo de la herencia es el acoplamiento: la hija depende de cómo está implementada la madre, no solo de lo que promete. Si la madre cambia un método del que depende la hija, la hija puede romperse sin que nadie la toque. Por eso la regla práctica es heredar solo cuando la relación es «es un» y la hija puede sustituir a la madre en cualquier sitio (principio de Liskov); en el resto de casos, composición.

Desde Java 17, sealed permite cerrar la jerarquía: sealed class Pago permits Tarjeta, Bizum dice que no hay más subclases posibles. Cada subclase permitida debe declararse final, sealed o non-sealed. A cambio, el compilador conoce todos los casos y un switch con patrones puede ser exhaustivo sin default.

La herencia reutiliza comportamiento, pero acopla mucho: la subclase depende de los detalles internos de la madre. Saber cuándo usarla y cuándo preferir composición es una pregunta clásica de diseño.

Se conecta con Composición frente a herenciaPrincipio de sustitución de LiskovSealed + switch

Compruébalo: ¿Compila final class PagoCripto extends Pago en otro fichero si no aparece en permits?

No. Solo las clases listadas en permits pueden heredar de una clase sealed; el compilador rechaza cualquier otra.

Cuatro bloques cronometrados. No pases al siguiente sin tener el resultado esperado.

  1. Bloque 1: Herencia y Clases Abstractas (0 - 25 min)

    Qué estudiar. extends, abstract, super y sealed ... permits.

    Pregunta a responder. ¿Qué ventaja tiene una jerarquía sealed frente a una abierta?

    Resultado esperado. Jerarquía sealed de tipos de pago con una clase abstracta y dos subclases.

  2. Bloque 2: Interfaces y Polimorfismo (25 - 50 min)

    Qué estudiar. Métodos default, static y private en interfaces; sobrescritura con @Override.

    Pregunta a responder. ¿Cuándo elegir clase abstracta y cuándo interfaz?

    Resultado esperado. Interfaz Notificador con dos implementaciones y un test que use ambas a través de la interfaz.

  3. Bloque 3: static, final y Enums (50 - 80 min)

    Qué estudiar. Orden de inicialización, campos static final y enums con métodos.

    Pregunta a responder. ¿Por qué no se debe guardar ordinal() en base de datos?

    Resultado esperado. Enum Estado con comportamiento y un EnumMap que cuente tareas por estado.

  4. Cierre de Consolidación (80 - 90 min)

    Qué estudiar. Repaso de la rúbrica del tutor.

    Pregunta a responder. Explicar en voz alta la diferencia entre sobrecarga y sobrescritura con un ejemplo.

    Resultado esperado. Nota con tres situaciones en las que usarías composición en vez de herencia.

Tres formas de comprobar si lo has entendido, de menos a más exigente: explicarlo con tus palabras, responder preguntas trampa y resolver el reto del tutor.

Explícalo desde cero

Lee cada ficha y explícala en voz alta antes de seguir: si puedes contárselo a alguien que empieza, lo has entendido.

Por qué se dice «favorece la composición sobre la herencia»

Lo que debes entender

Heredar une la subclase a los detalles internos de la madre: si la madre cambia, la hija puede romperse sin tocarla. Componer (tener un objeto como campo y delegar en él) solo te une a su interfaz pública.

Analogía para contarlo

Un coche no «es un» motor: tiene un motor. Si mañana pasas a uno eléctrico, cambias la pieza, no la familia del coche.

Lo que suele explicarse mal

Decir que la herencia es mala. Es correcta cuando la relación es de verdad «es un» y la subclase puede sustituir a la madre en cualquier sitio (principio de Liskov).

Señal de que aún no lo dominas

Si heredas de una clase solo para reutilizar uno o dos de sus métodos.

Preguntas trampa

Responde de memoria, en voz alta o por escrito, y después abre la respuesta razonada: lo importante es el porqué, no la palabra exacta.

Nivel 1 (Básico) ¿Puede una clase implementar dos interfaces que tienen el mismo método default? ¿Qué pasa? Ver respuesta

Respuesta razonada

Solo si la clase sobrescribe el método. Java no puede elegir entre dos implementaciones default con la misma firma, así que la clase que las hereda debe declarar su propia versión. Dentro puede delegar en una de ellas con A.super.metodo() o combinar las dos.

Si uno de los dos métodos viniera de una clase madre en lugar de una interfaz, no habría conflicto: el método de la clase gana siempre sobre el default de una interfaz («las clases ganan»).

Qué está evaluando la pregunta

Solo compila si la clase sobrescribe ese método y resuelve el conflicto; dentro puede llamar a una de las versiones con InterfazA.super.metodo(). Si no lo sobrescribe, el compilador da error por ambigüedad.

Nivel 2 (Intermedio) Si un método static está en la clase padre y en la hija con la misma firma, ¿hay polimorfismo al llamarlo desde una variable del tipo padre? Ver respuesta

Respuesta razonada

No. Un método static con la misma firma en la hija oculta al de la madre, pero no lo sobrescribe. La llamada se resuelve al compilar según el tipo declarado: Animal a = new Perro(); a.tipo() ejecuta Animal.tipo(). Por eso los IDE avisan cuando se llama a un método estático a través de una instancia: parece polimórfico y no lo es.

El polimorfismo solo existe para métodos de instancia que no sean private ni final, que la JVM resuelve en ejecución según la clase real del objeto.

Qué está evaluando la pregunta

No. Los métodos static no se sobrescriben, se ocultan: se elige al compilar según el tipo declarado de la variable, no según el objeto real. El polimorfismo solo funciona con métodos de instancia.

Nivel 3 (Entrevista Senior) ¿Por qué class Cuadrado extends Rectangulo es el ejemplo clásico de violación del principio de Liskov? Ver respuesta

Respuesta razonada

El principio de sustitución de Liskov dice que donde se usa la madre debe poder usarse cualquier hija sin que el programa se comporte mal. Un Rectangulo promete que ancho y alto se cambian por separado. Un Cuadrado no puede cumplirlo: o ignora uno de los setters o cambia los dos, y cualquier código que confíe en esa promesa falla.

En matemáticas un cuadrado es un rectángulo, pero en programación lo que importa es el comportamiento, no la taxonomía. Soluciones: hacer las figuras inmutables (sin setters, cada cambio crea una figura nueva) o no relacionarlas por herencia y que ambas implementen una interfaz Figura con area().

Qué está evaluando la pregunta

Porque un Rectangulo permite cambiar ancho y alto por separado, y un cuadrado no. Si el código que usa Rectangulo hace setAncho(5); setAlto(4) y espera área 20, con un Cuadrado obtiene otra cosa. La subclase no puede sustituir a la madre sin romper sus expectativas.

Reto del tutor

Un compañero propone crear BaseService<T> con save, findAll y delete, y que todos los servicios de la aplicación hereden de ella para no repetir código. Evalúa la propuesta.

Lo que una respuesta suele tener bien

Detecta código repetido y busca reutilizarlo.

Lo que suele quedarse en la superficie

Decir simplemente que «la herencia es mala» o que «es mejor usar interfaces».

El concepto de fondo que suele faltar

No ver que una clase base genérica obliga a todos los servicios a exponer operaciones que quizá no deben tener (no todos permiten borrar), los acopla a un único padre y mezcla lógica de negocio con acceso a datos.

Cómo lo respondería alguien senior en una entrevista

Los servicios representan casos de uso de negocio, no CRUD genérico, y lo genérico ya existe en Spring Data (JpaRepository). Si hay comportamiento común, mejor extraerlo a componentes que se inyectan (composición) o a interfaces pequeñas por capacidad. Herencia solo si todos los servicios «son un» mismo tipo y pueden sustituirse entre sí.

Proyecto «Pedidos» · paso 3 de 30

Estados del pedido y medios de pago

Modelar el ciclo de vida del pedido y los medios de pago con tipos que el compilador pueda comprobar.

De qué va el proyecto

El backend de una tienda online: clientes, catálogo de productos y pedidos con sus líneas, pagos y envíos. Empieza como un proyecto Maven con records y termina como un sistema con API REST, seguridad, mensajería, contenedores y observabilidad. Cada paso construye sobre el anterior, así que trabaja siempre en el mismo repositorio.

Qué hacer

  1. Crea enum EstadoPedido { BORRADOR, CONFIRMADO, PAGADO, ENVIADO, CANCELADO } con un método puedePasarA(EstadoPedido destino).
  2. Crea la clase Pedido (id, cliente, líneas, estado) con métodos confirmar(), pagar() y cancelar() que respetan las transiciones.
  3. Modela sealed interface MetodoPago permits Tarjeta, Bizum, Transferencia con records y una comisión distinta para cada uno.

Está listo cuando

  • Un pedido enviado no se puede cancelar ni un borrador pagar: lo impiden los métodos, no quien los llama.
  • Añadir un nuevo medio de pago obliga a tocar el cálculo de comisiones, porque el switch deja de compilar.

Katas: ejercicios cortos

Reglas de oro

  • Usa @Override siempre que sobrescribas: convierte un error silencioso en uno de compilación.
  • Prefiere composición; hereda solo si la subclase puede sustituir a la madre en cualquier sitio.
  • Nada de estado mutable en campos static.
  • Guarda los enums por nombre (EnumType.STRING), nunca por posición.

Java 17 frente a Java 21

CaracterísticaJava 17 LTSJava 21 LTS
Clases e interfaces sealed Estables (JEP 409) Estables y aprovechadas por switch con patrones
switch exhaustivo Sobre enums en switch expressions También sobre jerarquías sealed y records
Se guarda automáticamente

Fuentes oficiales

Revisado el . Este día en Markdown