Використання методу 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.