Programación Avanzada 2.º BT TI · DGETP / UTU
Profe MacónPROGRAMACIÓN AVANZADA
2.º BT TI · DGETP/UTU · 2026
CET1 · CET2
UNIDAD 01 · INTRODUCCIÓN A LA POO · LECCIÓN 05

Clases abstractas, sobrescritura y polimorfismo

¿Qué sucede cuando diferentes tipos de producto comparten una base, pero responden de manera distinta a la misma operación? Ampliamos «La Viñeta» para descubrirlo.

abstractextends@OverridesuperDespacho dinámico
01 · Punto de partida

¿Cuándo conviene especializar una clase?

En «La Viñeta» tenemos Producto, Cliente, Venta y DetalleVenta. Hasta ahora un producto tiene código, nombre, precio y stock. Supongamos que la tienda empieza a vender dos tipos de artículos con maneras distintas de calcular su precio final.

Diagrama UML de diseño original de La Viñeta
Modelo original: seguimos utilizando sus entidades. Las subclases de Producto que veremos a continuación son una ampliación exclusivamente didáctica.

Cómic

Ejemplo inventado: un cómic cuyo precio final tiene un recargo por edición especial.

Figura coleccionable

Ejemplo inventado: una figura con un recargo por embalaje de protección.

Decisión de análisis: no crearemos subclases porque sí. La herencia solo se justifica cuando necesitamos representar tipos verdaderamente especializados con comportamientos diferenciados. Si solo cambia un dato, puede ser suficiente un atributo.
02 · Concepto nuevo

¿Qué es una clase abstracta?

Una clase abstracta representa una base común para otras clases. Puede declarar atributos, constructores y métodos con código, pero también métodos abstractos, que no tienen implementación en esa clase.

«abstract» Productocodigo · nombre · precioBase · stock
calcularPrecioFinal() — abstracto
↙      ↘
ComicrecargoEdicion
calcularPrecioFinal()
FiguraColeccionablecostoProteccion
calcularPrecioFinal()
Producto.java — una base abstracta
public abstract class Producto {
    private long codigo;
    private String nombre;
    private double precioBase;
    private int stock;

    public Producto(long codigo, String nombre,
                    double precioBase, int stock) {
        this.codigo = codigo;
        this.nombre = nombre;
        this.precioBase = precioBase;
        this.stock = stock;
    }

    public String getNombre() { return nombre; }
    public double getPrecioBase() { return precioBase; }
    public long getCodigo() { return codigo; }
    public int getStock() { return stock; }

    public abstract double calcularPrecioFinal();
}

La palabra abstract impide crear directamente un objeto de Producto. Sí podremos declarar variables de tipo Producto para guardar referencias a objetos de clases concretas.

No confundir: el atributo precioBase es un dato común; el método abstracto calcularPrecioFinal() es una operación que las subclases deben concretar, salvo que ellas también sean abstractas.
03 · Sobrescritura

La misma operación, dos implementaciones

Cuando una subclase da su propia implementación de un método heredado hablamos de sobrescritura (override). Usamos @Override para pedirle al compilador que compruebe que realmente estamos sobrescribiendo un método.

Comic.java
public class Comic extends Producto {
    private double recargoEdicion;

    public Comic(long codigo, String nombre, double precioBase,
                 int stock, double recargoEdicion) {
        super(codigo, nombre, precioBase, stock);
        this.recargoEdicion = recargoEdicion;
    }

    @Override
    public double calcularPrecioFinal() {
        return getPrecioBase() + recargoEdicion;
    }
}
FiguraColeccionable.java
public class FiguraColeccionable extends Producto {
    private double costoProteccion;

    public FiguraColeccionable(long codigo, String nombre,
                               double precioBase, int stock,
                               double costoProteccion) {
        super(codigo, nombre, precioBase, stock);
        this.costoProteccion = costoProteccion;
    }

    @Override
    public double calcularPrecioFinal() {
        return getPrecioBase() + costoProteccion;
    }
}

Ambas clases son productos y comparten estado y operaciones. Pero calculan el precio final de manera distinta. En esta simplificación, los recargos son importes fijos en pesos uruguayos.

Importante: sobrescribir no es sobrecargar. Sobrescritura: una subclase redefine una operación heredada con una firma compatible. Sobrecarga: varios métodos comparten nombre, pero tienen distintos parámetros.
04 · Dos usos diferentes

super() no es lo mismo que super.metodo()

super(...)

Invoca el constructor de la superclase para inicializar la parte heredada. En nuestro ejemplo:

super(codigo, nombre, precioBase, stock);

super.metodo()

Invoca una implementación concreta heredada desde la superclase. No se puede invocar un método abstracto como si tuviera código.

super.descripcion();

Para observar super.metodo(), supongamos que agregamos a Producto un método concreto:

Fragmento adicional en Producto.java
public String descripcion() {
    return getCodigo() + " - " + getNombre();
}
Fragmento adicional en Comic.java
@Override
public String descripcion() {
    return super.descripcion() + " (cómic)";
}
Otra precisión: private mantiene los atributos encapsulados. Por eso las subclases consultan getPrecioBase() en lugar de acceder directamente al campo. El modificador protected existe, pero no necesitamos exponer atributos para que la herencia funcione.
05 · Idea fundamental

Polimorfismo por herencia

Una variable de tipo Producto puede contener una referencia a un Comic o una FiguraColeccionable. Cuando llamamos a un método sobrescrito, Java ejecuta la implementación del objeto real (despacho dinámico).

Main.java — programa de ejemplo
public class Main {
    public static void main(String[] args) {
        Producto a = new Comic(101, "Mafalda especial", 450, 5, 50);
        Producto b = new FiguraColeccionable(
                202, "Figura robot", 1200, 3, 180);

        System.out.println(a.calcularPrecioFinal()); // 500.0
        System.out.println(b.calcularPrecioFinal()); // 1380.0
    }
}

Tipo declarado

Ambas variables son Producto; desde ellas accedemos a las operaciones disponibles en ese tipo.

Tipo real del objeto

En a hay un Comic; en b, una FiguraColeccionable. Eso determina el cálculo.

No hace falta preguntar el tipo con if: cada objeto responde a calcularPrecioFinal() usando su propia implementación. Así podemos incorporar nuevos tipos de productos sin reescribir las operaciones generales que trabajan con Producto.
06 · Conexión con el SCORM anterior

Una lista de productos de diferentes tipos

El SCORM 04 presentó List, ArrayList y polimorfismo por interfaces. Ahora combinamos esos conocimientos con polimorfismo por herencia.

Ejemplo — recorrer productos polimórficos
import java.util.ArrayList;
import java.util.List;

public class CatalogoDemo {
    public static void main(String[] args) {
        List<Producto> catalogo = new ArrayList<>();
        catalogo.add(new Comic(101, "Mafalda", 450, 5, 50));
        catalogo.add(new FiguraColeccionable(
                202, "Figura robot", 1200, 3, 180));

        for (Producto producto : catalogo) {
            System.out.println(producto.getNombre() + " : $"
                    + producto.calcularPrecioFinal());
        }
    }
}

La lista admite ambos objetos porque ambos son subtipos de Producto. El recorrido no necesita conocer las clases concretas para calcular y mostrar el precio final.

Dos polimorfismos en el mismo ejemplo: List es una interfaz cuya implementación es ArrayList. Y Producto es una superclase cuyas implementaciones concretas son Comic y FiguraColeccionable.
07 · Diseñar con criterio

Clase abstracta e interfaz: ¿cuál utilizamos?

PreguntaClase abstractaInterfaz
¿Puede guardar estado de instancia?Sí, mediante atributos.No, no contiene campos de instancia.
¿Puede tener constructor?Sí, para inicializar la parte común.No.
¿Puede ofrecer métodos con implementación?Sí.Sí, por ejemplo con métodos default y static.
¿Cómo se utiliza?extends.implements.
¿Puede una clase usar varias?Una sola superclase directa.Puede implementar varias interfaces.

En «La Viñeta», una clase abstracta Producto podría reunir estado y operaciones comunes a tipos de productos. Una interfaz como List define operaciones disponibles para distintas estructuras de datos.

No transformar nuestro proyecto sin motivo: el modelo original de la tienda utiliza Producto como clase concreta. Este SCORM explora una variante didáctica alternativa. Si luego quisiéramos aplicarla al proyecto integrador, tendríamos que revisar constructores, instanciación y persistencia JDBC de manera coherente.
08 · Comprobación y documentación

¿Qué hemos aprendido?

1. ¿Qué instrucción construye correctamente una referencia polimórfica?

2. ¿Qué método se ejecuta al invocar p.calcularPrecioFinal() si el objeto real es un Comic?

3. ¿Para qué sirve super(...) en el constructor de Comic?

4. ¿Por qué conviene usar @Override?

Microentrega de documentación: dibuja un UML sencillo con Producto abstracta y sus dos subclases. Incluye el método abstracto, identifica cuáles métodos se sobrescriben y redacta dos oraciones para justificar por qué esta jerarquía sería útil —o innecesaria— para nuestra tienda.

Cierre de la unidad: pasamos de analizar entidades y responsabilidades a construir objetos, relacionarlos, organizarlos en colecciones y aprovechar herencia y polimorfismo. Ya tenemos una base para comprender Swing y la futura arquitectura de la aplicación.

Material en curso