Використання методу Java за замовчуванням


13

В протягом багатьох десятиліть це було так , що інтерфейси були тільки лише (тільки) для визначення сигнатури методи. Нам сказали, що це "правильний спосіб робити речі ™".

Потім вийшов Java 8 і сказав:

Ну, е-е, тепер ви можете визначити методи за замовчуванням. Треба бігти, до побачення.

Мені цікаво, як це засвоюється як досвідченими розробниками Java, так і тими, хто нещодавно (останні кілька років) почав його розробляти. Мені також цікаво, як це вписується в ортодоксальність і практику Java.

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

Я знаю, що це трохи відкрито, але тепер, коли вже було деякий час, щоб Java 8 використовувалася в реальних проектах, чи існує ще православ'я навколо використання методів за замовчуванням? Що я в основному бачу, коли їх обговорюють, - це те, як додати нові методи в інтерфейс, не порушуючи існуючих споживачів. А як же використовувати це з самого початку, як приклад, який я наводив вище. Хтось стикався з будь-якими проблемами із забезпеченням реалізації у своїх інтерфейсах?


Мене також зацікавила б ця перспектива. Я повертаюся на Яву через 6 років у світі .Net. Мені здається, що це може бути відповідь Java для методів розширення на C #, дещо впливає модульний метод Ruby. Я не грав з цим, тому не можу бути впевнений.
— Берін Лорич

1
Мені здається, що причина, чому вони додали методи за замовчуванням, є значною мірою для того, щоб вони могли розширити інтерфейси колекції без необхідності створювати зовсім інші інтерфейси
— Джастін,

1
@Justin: див. java.util.function.FunctionВикористання методів за замовчуванням у абсолютно новому інтерфейсі.
— Йорг W Міттаг

@Justin Думаю, що це був головний рушій. Я дійсно повинен почати звертати увагу на процес знову, оскільки вони почали дійсно робити зміни.
— 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інтерфейсу, але отримати все це для безпечного типу є складним і занадто довгим для цієї відповіді - я спробую зробити знімок, що не є безпечним для типу, не зберігає тип. Слідкуйте за налаштуваннями.
— Йорг W Міттаг

3
Приклади допомагають. Спасибі. Я неправильно зрозумів, що ви мали на увазі під одним методом інтерфейсу.
— JimmyJames

Що означає методи за замовчуванням, це те, що єдиний інтерфейс абстрактного методу більше не повинен бути інтерфейсом єдиного методу ;-)
— Jörg W Mittag

Я думав про це, і мені спало на думку, що AbstractCollection та AbstractList - це в основному те, про що ви тут говорите (2 метод замість 1, але я не думаю, що це вирішально.) Якби вони були перероблені як інтерфейси з методами defualt, це було б Будьте надзвичайно простим, щоб перетворити ітерабельний файл у колекцію, додавши розмір та створивши список із чого завгодно, це також оснащення, якщо ви можете проіндексувати та знати розмір.
— JimmyJames
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.