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));