Un excellent cas d'utilisation est ce que j'appelle des interfaces «à levier»: des interfaces qui n'ont qu'un petit nombre de méthodes abstraites (idéalement 1), mais qui fournissent beaucoup de «levier» en ce qu'elles vous offrent beaucoup de fonctionnalités: vous seulement besoin d'implémenter 1 méthode dans votre classe mais obtenez beaucoup d'autres méthodes "gratuitement". Pensez à une interface de collecte, par exemple, avec une seule abstraite foreach
méthode et des default
méthodes comme map
, fold
, reduce
, filter
, partition
, groupBy
, sort
, sortBy
, etc.
Voici quelques exemples. Commençons par java.util.function.Function<T, R>
. Il a une seule méthode abstraite R apply<T>
. Et il a deux méthodes par défaut qui vous permettent de composer la fonction avec une autre fonction de deux manières différentes, avant ou après. Ces deux méthodes de composition sont implémentées en utilisant simplementapply
:
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));
}
Vous pouvez également créer une interface pour des objets comparables, quelque chose comme ceci:
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());
}
}
Ou un framework de collections extrêmement simplifié, où toutes les opérations de collections retournent Collection
, quel que soit le type d'origine:
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)
}
}
Cela devient très intéressant en combinaison avec lambdas, car une telle interface "levier" peut être implémentée par un lambda (c'est une interface SAM).
C'est le même cas d'utilisation que les méthodes d'extension ont été ajoutées en C♯, mais les méthodes par défaut ont un avantage distinct: ce sont des méthodes d'instance "correctes", ce qui signifie qu'elles ont accès aux détails d'implémentation privée de l'interface ( private
les méthodes d'interface arrivent en Java 9), alors que les méthodes d'extension ne sont que du sucre syntaxique pour les méthodes statiques.
Si Java obtenait une injection d'interface, il permettrait également une correction de singe modulaire sécurisée et de portée. Cela serait très intéressant pour les implémenteurs de langage sur la JVM: pour le moment, par exemple, JRuby hérite ou encapsule des classes Java pour leur fournir une sémantique Ruby supplémentaire, mais idéalement, ils veulent utiliser les mêmes classes. Avec l'Injection d'Interface et les Méthodes par Défaut, ils pourraient injecter par exemple une RubyObject
interface dans java.lang.Object
, de sorte qu'un Java Object
et un Ruby Object
sont exactement la même chose .