Un gran caso de uso es lo que yo llamo interfaces de "palanca": interfaces que solo tienen una pequeña cantidad de métodos abstractos (idealmente 1), pero proporcionan mucha "influencia" en el sentido de que le brindan mucha funcionalidad: usted solo necesitas implementar 1 método en tu clase pero obtienes muchos otros métodos "gratis". Piense en una interfaz de recogida, por ejemplo, con un solo extracto foreach
método y default
métodos como map
, fold
, reduce
, filter
, partition
, groupBy
, sort
, sortBy
, etc.
Aquí hay un par de ejemplos. Vamos a empezar con java.util.function.Function<T, R>
. Tiene un solo método abstracto R apply<T>
. Y tiene dos métodos predeterminados que le permiten componer la función con otra función de dos maneras diferentes, ya sea antes o después. Ambos métodos de composición se implementan utilizando soloapply
:
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
return (V v) -> apply(before.apply(v));
}
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
return (T t) -> after.apply(apply(t));
}
También podría crear una interfaz para objetos comparables, algo como esto:
interface MyComparable<T extends MyComparable<T>> {
int compareTo(T other);
default boolean lessThanOrEqual(T other) {
return compareTo(other) <= 0;
}
default boolean lessThan(T other) {
return compareTo(other) < 0;
}
default boolean greaterThanOrEqual(T other) {
return compareTo(other) >= 0;
}
default boolean greaterThan(T other) {
return compareTo(other) > 0;
}
default boolean isBetween(T min, T max) {
return greaterThanOrEqual(min) && lessThanOrEqual(max);
}
default T clamp(T min, T max) {
if (lessThan( min)) return min;
if (greaterThan(max)) return max;
return (T)this;
}
}
class CaseInsensitiveString implements MyComparable<CaseInsensitiveString> {
CaseInsensitiveString(String s) { this.s = s; }
private String s;
@Override public int compareTo(CaseInsensitiveString other) {
return s.toLowerCase().compareTo(other.s.toLowerCase());
}
}
O un marco de colecciones extremadamente simplificado, donde todas las operaciones de colecciones regresan Collection
, independientemente del tipo original:
interface MyCollection<T> {
void forEach(java.util.function.Consumer<? super T> f);
default <R> java.util.Collection<R> map(java.util.function.Function<? super T, ? extends R> f) {
java.util.Collection<R> l = new java.util.ArrayList();
forEach(el -> l.add(f.apply(el)));
return l;
}
}
class MyArray<T> implements MyCollection<T> {
private T[] array;
MyArray(T[] array) { this.array = array; }
@Override public void forEach(java.util.function.Consumer<? super T> f) {
for (T el : array) f.accept(el);
}
@Override public String toString() {
StringBuilder sb = new StringBuilder("(");
map(el -> el.toString()).forEach(s -> { sb.append(s); sb.append(", "); } );
sb.replace(sb.length() - 2, sb.length(), ")");
return sb.toString();
}
public static void main(String... args) {
MyArray<Integer> array = new MyArray<>(new Integer[] {1, 2, 3, 4});
System.out.println(array);
// (1, 2, 3, 4)
}
}
Esto se vuelve muy interesante en combinación con lambdas, porque dicha interfaz "palanca" puede ser implementada por un lambda (es una interfaz SAM).
Este es el mismo caso de uso para el que se agregaron los Métodos de extensión en C♯, pero los métodos predeterminados tienen una clara ventaja: son métodos de instancia "adecuados", lo que significa que tienen acceso a detalles de implementación privados de la interfaz ( private
están llegando los métodos de interfaz en Java 9), mientras que los métodos de extensión son solo azúcar sintáctico para los métodos estáticos.
Si Java recibiera alguna vez una inyección de interfaz, también permitiría parches de mono modulares, seguros y de tipo seguro. Esto sería muy interesante para los implementadores de lenguaje en la JVM: en este momento, por ejemplo, JRuby hereda o ajusta las clases de Java para proporcionarles semánticas de Ruby adicionales, pero idealmente, quieren usar las mismas clases. Con la inyección de interfaz y los métodos predeterminados, podrían inyectar, por ejemplo, una RubyObject
interfaz java.lang.Object
, de modo que un Java Object
y un Ruby Object
son exactamente lo mismo .