¿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.

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.
¿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.
calcularPrecioFinal() — abstracto
calcularPrecioFinal()
calcularPrecioFinal()
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.
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.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.
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;
}
}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.
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:
public String descripcion() {
return getCodigo() + " - " + getNombre();
}@Override
public String descripcion() {
return super.descripcion() + " (cómic)";
}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.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).
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.
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.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.
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.
List es una interfaz cuya implementación es ArrayList. Y Producto es una superclase cuyas implementaciones concretas son Comic y FiguraColeccionable.Clase abstracta e interfaz: ¿cuál utilizamos?
| Pregunta | Clase abstracta | Interfaz |
|---|---|---|
| ¿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.
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.¿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?
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.
PROGRAMACIÓN AVANZADA