Interfaces frente a Clases Abstractas

Contratos y esqueletos de implementación

Interfaces

Una interfaz define un contrato: qué operaciones ofrece un tipo, sin decir cómo las implementa. Desde Java 8 pueden tener default methods (implementación heredable) y static methods (factorías), pero no pueden tener estado de instancia.

// Interfaz: contrato puro + default methods desde Java 8
public interface Comparable<T> {
    int compareTo(T otro); // abstracto: obliga a implementar
}

public interface Serializable {
    // sin métodos — solo marca semántica (marker interface)
}

// Java 8: puede tener métodos con implementación
public interface Coleccion<E> {
    void agregar(E elemento);    // abstracto
    int size();                  // abstracto

    default boolean isEmpty() {  // default: implementación opcional
        return size() == 0;
    }

    static <E> Coleccion<E> vacia() { // static factory method
        return new ColeccionVacia<>();
    }
}

Clases abstractas

Una clase abstracta es una clase que no puede instanciarse directamente. Puede combinar métodos abstractos (sin implementar) con métodos concretos, constructores y estado. El patrón Template Method — definir el esqueleto de un algoritmo dejando pasos a las subclases — es el uso más habitual.

// Clase abstracta: esqueleto de implementación con estado compartido
public abstract class ConexionBBDD {
    protected final String url;
    protected Connection connection; // estado compartido entre subclases

    protected ConexionBBDD(String url) {
        this.url = url;
    }

    // Método abstracto: las subclases definen cómo abrir la conexión
    protected abstract Connection abrirConexion() throws SQLException;

    // Template method: define el algoritmo, delega pasos a subclases
    public final void conectar() throws SQLException {
        System.out.println("Conectando a " + url);
        connection = abrirConexion(); // delegado a la subclase
        configurarConexion();
    }

    protected void configurarConexion() {
        // implementación por defecto: no hace nada
        // las subclases pueden sobreescribir si necesitan configurar
    }
}

Diferencias clave

// INTERFACES: qué no pueden hacer (antes de Java 8)
// - No pueden tener estado (campos de instancia)
// - No pueden tener constructores
// - Los campos son implícitamente public static final (constantes)

// INTERFACES: qué pueden desde Java 8
// - Default methods (implementación con 'default')
// - Static methods

// CLASES ABSTRACTAS: qué pueden que las interfaces no
// - Estado (campos de instancia)
// - Constructores
// - Campos con cualquier modificador de acceso
// - Múltiples niveles de herencia

// HERENCIA MÚLTIPLE:
// Una clase puede implementar N interfaces pero extender solo 1 clase abstracta
class MiClase extends ClaseAbstracta implements InterfazA, InterfazB { ... }

Cuándo usar cada uno

// Cuándo usar interfaz:
// → Contrato sin implementación que múltiples tipos no relacionados deben cumplir
// → Herencia múltiple de tipo (una clase puede implementar varias)
// → API pública que evolucionará sin romper implementaciones existentes (default methods)

public interface Exportable {
    byte[] exportar(Formato formato);

    default byte[] exportarCSV() {
        return exportar(Formato.CSV);
    }
}

// Cuándo usar clase abstracta:
// → Jerarquía de herencia con estado compartido
// → Template Method pattern: algoritmo fijo con pasos variables
// → Cuando las subclases comparten código de implementación sustancial

public abstract class InformeBase {
    private final String titulo;

    protected InformeBase(String titulo) { this.titulo = titulo; }

    public final String generar() {
        return encabezado() + cuerpo() + pie();
    }

    protected String encabezado() { return titulo + "\n"; }
    protected abstract String cuerpo();
    protected String pie() { return "---\n"; }
}
Effective Java (ítem 20): prefiere interfaces a clases abstractas. Las interfaces permiten herencia múltiple de tipo, son más fáciles de implementar en tipos existentes y no imponen una jerarquía de clases. Usa clases abstractas cuando necesites compartir estado o implementación sustancial entre subclases.

Siguiente apartado → Sobrecarga y sobreescritura

Índice de la sección

Índice del curso