Default methods en interfaces

Contratos con implementación por defecto (Java 8)

El problema que resuelven

Antes de Java 8, las interfaces eran contratos puros: solo métodos abstractos. Añadir un nuevo método a una interfaz publicada rompía todas las clases que la implementaban. El JDK no podía evolucionar sus interfaces sin romper compatibilidad binaria.

// Antes de Java 8: añadir un método a una interfaz rompía todas las implementaciones
public interface Coleccion {
    void agregar(Object elemento);
    void eliminar(Object elemento);
    // Si añadimos forEach aquí, todas las clases que implementen Coleccion
    // dejan de compilar hasta que implementen el método
}

Con default methods, Java 8 permite añadir métodos con implementación a una interfaz sin romper las clases existentes. Fue la vía que usó el JDK para añadir forEach, stream() y sort() a las interfaces de colecciones.

public interface Coleccion {
    void agregar(Object elemento);
    void eliminar(Object elemento);

    // Default method: tiene implementación, no obliga a las subclases
    default void vaciar(Coleccion otra) {
        // las implementaciones existentes heredan este comportamiento gratis
        System.out.println("Operación no soportada por defecto");
    }
}

Sintaxis y uso

Un default method se declara con la palabra clave default y tiene cuerpo. Las clases que implementan la interfaz pueden sobreescribirlo o heredarlo tal cual.

public interface Saludable {
    String getNombre(); // abstracto: obliga a implementar

    default String saludar() {
        return "Hola, soy " + getNombre();
    }

    default String saludarFormalmente() {
        return "Buenos días, me llamo " + getNombre();
    }
}

public class Persona implements Saludable {
    private String nombre;

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

    @Override
    public String getNombre() { return nombre; } // único método obligatorio

    // saludar() y saludarFormalmente() se heredan sin necesidad de implementarlos
}

Conflicto de herencia múltiple

Java permite implementar varias interfaces. Si dos interfaces definen un default method con la misma firma, la clase que las implemente debe sobreescribirlo — el compilador lo exige para evitar ambigüedad.

// Dos interfaces con default methods del mismo nombre
public interface A {
    default String mensaje() { return "A"; }
}

public interface B {
    default String mensaje() { return "B"; }
}

// Conflicto: la clase DEBE sobreescribir mensaje() para resolver la ambigüedad
public class C implements A, B {
    @Override
    public String mensaje() {
        return A.super.mensaje(); // se elige explícitamente cuál usar
    }
}

Default methods en el JDK

El caso más visible es Iterable.forEach, que es un default method. Gracias a él, cualquier colección del JDK ganó forEach sin que sus implementaciones cambiaran una sola línea.

// Así está definido forEach en Iterable<T> en el JDK:
public interface Iterable<T> {
    Iterator<T> iterator(); // abstracto

    default void forEach(Consumer<? super T> action) {
        for (T t : this) {
            action.accept(t);
        }
    }
}

// Gracias a esto, List, Set y cualquier Iterable tienen forEach sin cambiar su código
List<String> nombres = List.of("Ana", "Luis", "Eva");
nombres.forEach(n -> System.out.println(n));
Diferencia clave con clase abstracta: los default methods permiten herencia múltiple de comportamiento (una clase puede implementar N interfaces con default methods); una clase abstracta solo puede extenderse una vez. Úsalos para añadir comportamiento opcional o de utilidad a una interfaz, no para sustituir la clase abstracta cuando necesites estado compartido.

Siguiente sección → Sistema de tipos, sintaxis y colecciones

Índice de la sección

Índice del curso