Los cuatro pilares en Java

Herencia simple, encapsulamiento, polimorfismo y abstracción

Herencia simple

Java soporta herencia simple: una clase solo puede extender una clase padre (extends). Esto evita el problema del diamante de C++, pero limita la reutilización. Las interfaces compensan esta restricción permitiendo implementación múltiple.

// Herencia: una clase extiende otra para reutilizar y especializar comportamiento
public class Animal {
    protected String nombre;

    public Animal(String nombre) {
        this.nombre = nombre;
    }

    public void hacerSonido() {
        System.out.println(nombre + " hace un sonido");
    }
}

public class Perro extends Animal {
    public Perro(String nombre) {
        super(nombre); // llama al constructor de Animal
    }

    @Override
    public void hacerSonido() {
        System.out.println(nombre + " ladra"); // especialización
    }
}

// Uso:
Animal a = new Perro("Rex");
a.hacerSonido(); // "Rex ladra" — polimorfismo en acción

Encapsulamiento

Ocultar el estado interno de un objeto y exponer solo las operaciones válidas. El objetivo no es la seguridad sino mantener los invariantes de la clase: condiciones que siempre deben ser verdad sobre el estado interno.

// Encapsulamiento: ocultar el estado interno, exponer solo la interfaz pública
public class CuentaBancaria {
    private double saldo; // privado: nadie puede modificarlo directamente

    public CuentaBancaria(double saldoInicial) {
        if (saldoInicial < 0) throw new IllegalArgumentException("Saldo negativo");
        this.saldo = saldoInicial;
    }

    public void depositar(double importe) {
        if (importe <= 0) throw new IllegalArgumentException("Importe inválido");
        saldo += importe;
    }

    public boolean retirar(double importe) {
        if (importe > saldo) return false;
        saldo -= importe;
        return true;
    }

    public double getSaldo() { return saldo; }
}
// El invariante (saldo >= 0) queda garantizado internamente

Polimorfismo

Un objeto puede ser tratado como su tipo padre. En Java el polimorfismo de subtipo se implementa mediante dynamic dispatch: la JVM decide en tiempo de ejecución qué implementación de un método invocar según el tipo real del objeto, no el tipo declarado.

// Polimorfismo: mismo método, comportamiento distinto según el tipo real
public abstract class Forma {
    public abstract double area();
}

public class Circulo extends Forma {
    private double radio;
    public Circulo(double radio) { this.radio = radio; }
    @Override
    public double area() { return Math.PI * radio * radio; }
}

public class Rectangulo extends Forma {
    private double ancho, alto;
    public Rectangulo(double ancho, double alto) {
        this.ancho = ancho; this.alto = alto;
    }
    @Override
    public double area() { return ancho * alto; }
}

// El llamador no necesita saber qué tipo concreto es
List<Forma> formas = Arrays.asList(new Circulo(5), new Rectangulo(3, 4));
for (Forma f : formas) {
    System.out.println(f.area()); // dispatch en tiempo de ejecución
}

Abstracción

Modelar un concepto por lo que hace (contrato), no por cómo lo hace (implementación). En Java se expresa mediante interfaces y clases abstractas. La abstracción es la base del principio de inversión de dependencias: el código de alto nivel depende de abstracciones, no de implementaciones concretas.

// Abstracción: exponer qué hace algo, ocultar cómo lo hace
public interface Repositorio<T> {
    T buscarPorId(Long id);
    void guardar(T entidad);
    void eliminar(Long id);
}

// El llamador trabaja con la abstracción, no con la implementación concreta
public class ServicioUsuarios {
    private final Repositorio<Usuario> repo; // depende de la abstracción

    public ServicioUsuarios(Repositorio<Usuario> repo) {
        this.repo = repo;
    }

    public Usuario obtenerUsuario(Long id) {
        return repo.buscarPorId(id); // no sabe si es BBDD, memoria, API...
    }
}
Los cuatro pilares juntos: la herencia permite reutilización; el encapsulamiento protege los invariantes; el polimorfismo permite sustituir implementaciones sin cambiar el llamador; la abstracción desacopla el contrato de la implementación. En código real se usan los cuatro de forma combinada.

Siguiente apartado → Composición frente a herencia

Índice de la sección

Índice del curso