Использование метода Java по умолчанию

13

В течение многих десятилетий это было так , что интерфейсы были только только (только) для определения сигнатуры методы. Нам сказали, что это был «правильный способ делать вещи».

Затем вышла Java 8 и сказала:

Ну, теперь вы можете определить методы по умолчанию. Должен бежать, пока.

Мне любопытно, как это усваивается как опытными разработчиками Java, так и теми, кто недавно (в последние несколько лет) начал его разработку. Мне также интересно, как это вписывается в ортодоксальность и практику Java.

Я создаю некоторый экспериментальный код, и в то время как я делал некоторый рефакторинг, я получил интерфейс, который просто расширяет стандартный интерфейс (Iterable) и добавляет два метода по умолчанию. И я буду честен, мне чертовски хорошо от этого.

Я знаю, что это немного открытый, но теперь, когда Java 8 уже использовалось в реальных проектах, есть ли ортодоксальность в использовании методов по умолчанию? Когда я обсуждаю их, я чаще всего вижу, как добавлять новые методы в интерфейс, не нарушая существующих потребителей. Но как насчет использования этого с самого начала, как в примере, который я привел выше. Кто-нибудь сталкивался с какими-либо проблемами с предоставлением реализаций в их интерфейсах?

JimmyJames
источник
Я также был бы заинтересован в этой перспективе. Я возвращаюсь на Java через 6 лет в мире .Net. Мне кажется, что это может быть ответом Java для методов расширения C #, с небольшим влиянием от методов модуля Ruby. Я не играл с этим, поэтому я не уверен.
Берин Лорич
1
Мне кажется, что причина, по которой они добавили методы по умолчанию, заключается в том, что они могут расширять интерфейсы сбора данных, не
Джастин,
1
@Justin: ознакомьтесь java.util.function.Functionс использованием методов по умолчанию в новом интерфейсе.
Йорг Миттаг,
@ Джастин Я думаю, что это был основной драйвер. Я должен снова начать уделять внимание процессу, так как они действительно начали вносить изменения.
JimmyJames,

Ответы:

12

Отличным вариантом использования являются то, что я называю «рычажными» интерфейсами: интерфейсы, которые имеют только небольшое количество абстрактных методов (в идеале 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- это одно и то же .

Йорг Миттаг
источник
1
Я не полностью следую этому. Метод по умолчанию в интерфейсе должен быть определен в терминах других методов интерфейса или методов, определенных в Object. Можете ли вы привести пример того, как создать осмысленный интерфейс с одним методом методом по умолчанию? Если вам нужен синтаксис Java 9 для демонстрации, это нормально.
JimmyJames
Например: в Comparableинтерфейсе с абстрактным compareToспособом, и по умолчанию lessThan, lessThanOrEqual, greaterThan, greaterThanOrEqual, isBetween, и clampметоды, все реализованы в терминах compareTo. Или просто посмотрите java.util.function.Function: у него есть абстрактный applyметод и два метода композиции по умолчанию, оба реализованы в терминах apply. Я попытался привести пример Collectionинтерфейса, но сделать все это безопасным для типов сложно и слишком долго для этого ответа - я попытаюсь дать вариант, не безопасный для типов и сохраняющий тип. Будьте на связи.
Йорг Миттаг,
3
Примеры помогают. Благодарю. Я неправильно понял, что вы имели в виду под интерфейсом с одним методом.
JimmyJames,
Методы по умолчанию означают, что единственный интерфейс абстрактного метода больше не должен быть интерфейсом одного метода ;-)
Jörg W Mittag
Я думал об этом, и мне пришло в голову, что AbstractCollection и AbstractList - это в основном то, о чем вы говорите здесь (2 метода вместо 1, но я не думаю, что это важно). Если бы они были преобразованы как интерфейсы с методами defualt, это было бы очень просто превратить итерируемое в коллекцию, добавив размер и создав список из чего угодно, также легко, если вы можете индексировать и знать размер.
JimmyJames