Чудовим випадком використання є те, що я називаю "важільними" інтерфейсами: інтерфейси, які мають лише невелику кількість абстрактних методів (в ідеалі 1), але дають багато "важелів", оскільки вони надають вам велику функціональність: ви тільки Вам потрібно реалізувати 1 метод у своєму класі, але отримати багато інших методів "безкоштовно". Подумайте інтерфейс збору, наприклад, за допомогою одного абстрактного foreach
методу і default
методів , такі як map
, fold
, reduce
, filter
, partition
, groupBy
, sort
, sortBy
і т.д.
Ось пара прикладів. Почнемо з java.util.function.Function<T, R>
. Він має єдиний абстрактний метод R apply<T>
. І він має два методи за замовчуванням, які дозволяють вам складати функцію з іншою функцією двома різними способами - до чи після. Обидва ці методи композиції реалізовані за допомогою простоapply
:
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));
}
Ви також можете створити інтерфейс для порівняних об'єктів, приблизно такий:
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());
}
}
Або надзвичайно спрощена структура колекцій, де всі операції з колекції повертаються Collection
, незалежно від того, який тип був оригінальним:
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)
}
}
Це стає дуже цікавим у поєднанні з лямбдами, тому що такий "важільний" інтерфейс може бути реалізований лямбда (це інтерфейс SAM).
Це той самий випадок використання, який були додані Методи розширення в C♯, але методи за замовчуванням мають одну виразну перевагу: вони є "належними" методами екземплярів, це означає, що вони мають доступ до приватних деталей реалізації інтерфейсу ( private
методи інтерфейсу надходять в Java 9), тоді як методи розширення є лише синтаксичним цукром для статичних методів.
Якщо Java коли-небудь отримає інтерфейсну ін'єкцію, це також дозволить забезпечити безпечний тип, модульний виправлення мавп. Це було б дуже цікаво для мовних реалізаторів JVM: на даний момент, наприклад, JRuby або успадковує, або завершує Java-класи, щоб надати їм додаткову семантику Ruby, але в ідеалі вони хочуть використовувати ті самі класи. За допомогою методів ін'єкцій інтерфейсу та методів за замовчуванням вони можуть вводити, наприклад, RubyObject
інтерфейс у java.lang.Object
, так що Java Object
та Ruby Object
- це те саме .