Conceptos Avanzados de Programación Orientada a Objetos en Java: Herencia, Polimorfismo y Colecciones

Enviado por Chuletator online y clasificado en Informática y Telecomunicaciones

Escrito el en español con un tamaño de 7,99 KB

Conceptos Avanzados de Programación Orientada a Objetos (POO) en Java

1. Clase Padre (Abstracta)

La clase abstracta define una estructura común y obliga a las subclases a implementar ciertos comportamientos.

public abstract class Producto {
    // PROTECTED: Para que los hijos (Pila, Pintura) accedan directamente
    protected String nombre;
    protected double precioBase;

    public Producto(String nombre, double precioBase) {
        this.nombre = nombre;
        this.precioBase = precioBase;
    }

    // Método abstracto: Obliga a los hijos a definir su propia fórmula
    public abstract double calcularPVP();

    // Método común: Todos lo usan igual (reutilización)
    public String getNombre() { return nombre; }
}

2. Colecciones y Polimorfismo

El polimorfismo permite tratar objetos de diferentes clases (hijos) como si fueran del tipo de la clase común (padre).

Se utiliza la clase más genérica a la izquierda (ej. List o Producto):

ArrayList<Producto> inventario = new ArrayList<>();

Uso del Polimorfismo

Se añaden instancias de diferentes clases:

inventario.add(new Pila("AAA", 5.0, "Alcalina"));
inventario.add(new Pintura("Blanca", 20.0, 5));

Recorrido y Despacho Dinámico

Mediante un bucle for-each, Java determina automáticamente qué versión de calcularPVP() usar:

for (Producto p : inventario) {
    // Java sabe automáticamente qué versión de calcularPVP() usar
    System.out.println(p.getNombre() + " - " + p.calcularPVP());

    // ⚠️ PELIGRO: Si necesitas un atributo exclusivo del hijo (ej. tipo de Pila)
    if (p instanceof Pila) {
        Pila laPila = (Pila) p; // Casteo explícito
        System.out.println("Es pila tipo: " + laPila.getTipo());
    }
}

3. La palabra clave final

Se utiliza cuando un método no debe ser sobrescrito por ninguna subclase. Ejemplo:

"El cálculo del IVA es inamovible y ninguna subclase debe poder modificarlo".

public final double calcularIVA() {
    return this.precio * 0.21;
}

4. Ordenamiento de Colecciones

A. Orden Natural (Implementando Comparable)

Se define DENTRO de la clase Producto y establece el orden "por defecto".

public abstract class Producto implements Comparable<Producto> {
    // ... atributos ...
    @Override
    public int compareTo(Producto otro) {
        // Para Strings:
        return this.referencia.compareTo(otro.referencia);
        // Para Ints: return this.stock - otro.stock;
    }
}
// Uso: Collections.sort(inventario);

B. Orden "a la carta" (Usando Comparator)

Se define FUERA (en el main o clase controladora) para ordenar por criterios específicos (nombre, precio, etc.).

Truco Lambda (Fácil de memorizar):

(p1, p2) -> p1.getLoQueSea().compareTo(p2.getLoQueSea())

Collections.sort(inventario, (p1, p2) -> p1.getNombre().compareTo(p2.getNombre()));

5. Formateo de Salida (Tabla)

Se usa System.out.printf para alinear columnas:

System.out.printf("%\-10s %\-20s %\-5d %.2f€\n", 
                producto.getRef(), 
                producto.getNombre(), 
                producto.getStock(), 
                producto.getPrecio());

6. La Trampa de Borrar en un Bucle (Uso de Iterator)

Para eliminar elementos de una colección mientras se itera, se debe usar el método remove() del iterador, no el de la lista, para evitar ConcurrentModificationException.

Iterator<Producto> it = inventario.iterator();
while (it.hasNext()) {
    Producto p = it.next();
    if (p.getStock() == 0) {
        it.remove(); // Usar el remove del iterador, NO de la lista
    }
}

Opción B (Moderna - "Pro"):

Usando expresiones lambda para eliminar condicionalmente:

// "Elimina si el stock es cero"
inventario.removeIf(p -> p.getStock() == 0);

7. Casteo Condicional Específico

Cuando se necesita acceder a métodos exclusivos de una subclase (ej. Ultramarino), se debe verificar el tipo y luego realizar el casteo (conversión de referencia).

La Solución (Código obligatorio):

for (Producto p : inventario) {
    // 1. Preguntar: ¿Es este producto una instancia de Ultramarino?
    if (p instanceof Ultramarino) {
        // 2. Castear (Convertir la referencia): "Baja" de Producto a Ultramarino
        Ultramarino u = (Ultramarino) p; 
        // 3. Ahora SÍ puedes usar métodos exclusivos del hijo
        System.out.println("Caduca el: " + u.getCaducidad());
    }
}

8. Implementación del método equals()

Es vital para el correcto funcionamiento de métodos como remove() y contains() en colecciones.

Implementación en la clase Producto (Padre)

@Override
public boolean equals(Object obj) {
    // 1. Si es el mismo objeto en memoria -> true
    if (this == obj) return true;
    // 2. Si es null o no es un Producto -> false
    if (obj == null || getClass() != obj.getClass()) return false;
    
    // 3. Casteamos para poder comparar atributos
    Producto otro = (Producto) obj;  
    
    // 4. Comparamos lo que NOSOTROS consideramos identidad (la referencia/ID)
    // OJO: Usar .equals() para Strings, nunca ==
    return this.referencia.equals(otro.referencia);
}

9. Estructuras de Datos Adicionales

Set (Conjuntos) para evitar duplicados

Los Sets garantizan que no habrá elementos repetidos. No guardan el orden de inserción (a menos que se use LinkedHashSet).

import java.util.HashSet;
import java.util.Set;

Set<String> correos = new HashSet<>();

correos.add("[email protected]");
boolean agregado = correos.add("[email protected]"); // Devuelve FALSE, no lo añade.

System.out.println(correos.size()); // Imprime 1, no 2.

10. Manejo de Errores con try-catch

Permite capturar excepciones (errores en tiempo de ejecución) sin que el programa se detenga.

int opcion = -1; // Valor por defecto inválido
boolean datoCorrecto = false;

while (!datoCorrecto) {
    try {
        System.out.print("Introduce opción: ");
        // Si el usuario mete letras, aquí explota y salta al catch
        opcion = Integer.parseInt(sc.nextLine()); 
        datoCorrecto = true; // Si llega aquí, todo fue bien
    } catch (NumberFormatException e) {
        // Aquí capturamos el error sin que el programa se cierre
        System.out.println("¡Error! Debes introducir un número entero.");
    }
}

11. El "Busca-Rápido": HashMap (Diccionarios)

Permite acceder a elementos directamente mediante una clave, ofreciendo búsquedas casi instantáneas (O(1)).

import java.util.HashMap;
import java.util.Map;

// Declaración: <Clave, Valor> -> <String referencia, Producto objeto>
Map<String, Producto> inventarioMap = new HashMap<>();

// 1. AÑADIR (put en vez de add)
inventarioMap.put("REF001", new Pila("Duracell", 5.0, "AA"));
inventarioMap.put("REF002", new Ultramarino("Leche", 1.0, fCad));

// 2. BUSCAR (get) -> ¡Sin bucles for! Es instantáneo.
Producto p = inventarioMap.get("REF001"); 

if (p != null) {
    System.out.println("Encontrado: " + p.getNombre());
} else {
    System.out.println("No existe esa referencia");
}

// 3. RECORRER (Usando .values() para obtener solo los objetos)
for (Producto prod : inventarioMap.values()) {
    System.out.println(prod);
}

Entradas relacionadas: