Как методы Java 8 по умолчанию помогают с лямбдами?

В этой статье утверждается, что:

Одной из основных причин введения методов по default в интерфейсах является усовершенствование API коллекций в Java 8 для поддержки лямбда-выражений.

Я мог понять, что @FunctionalInterface помог, сказав, что существует ТОЛЬКО один абстрактный метод, и лямбда-выражение должно представлять этот конкретный метод.

Но почему методы по default помогают поддерживать лямбды?

Ответ 1

В качестве примера рассмотрим метод Collection.forEach, который предназначен для использования экземпляра функционального интерфейса Consumer и имеет реализацию по умолчанию в интерфейсе Collection:

default void forEach(Consumer<? super T> action) {
    Objects.requireNonNull(action);
    for (T t : this) {
        action.accept(t);
    }
}

Если бы разработчики JDK не представили концепцию методов по умолчанию, то все реализующие классы интерфейса Collection должны были бы реализовать метод forEach поэтому было бы проблематично переключиться на Java-8, не нарушая ваш код.

Таким образом, чтобы облегчить принятие лямбда-выражений и использование новых функциональных интерфейсов, таких как Consumer, Supplier, Predicate и т.д., Дизайнеры JDK представили концепцию методов по умолчанию для обеспечения обратной совместимости, и теперь проще переключаться на Java - 8, не делая любые изменения.

Если вам не нравится реализация по умолчанию в интерфейсе, вы можете переопределить ее и предоставить свою собственную.

Ответ 2

Они помогли косвенно: вы можете использовать лямбда-выражения в коллекциях благодаря дополнительным методам, таким как removeIf(), stream() и т.д.

Эти методы не могли быть добавлены в коллекции без полного нарушения существующих реализаций коллекции, если бы они не были добавлены в качестве методов по умолчанию.

Ответ 3

Другая ситуация, когда методы по умолчанию помогают в большей степени, - это сами функциональные интерфейсы. Возьмем Function<T,R> к примеру, интерфейс Function<T,R> Единственный метод, который вас действительно волнует, это R apply(T t), поэтому, когда вам нужна Function где-то, вы можете передать лямбду, и она создаст экземпляр Function где эта лямбда Метод - это метод apply.

Однако когда у вас есть экземпляр Function, вы можете вызывать другие полезные методы, такие как <V> Function<T,V> andThen(Function<? super R,? extends V> after) которые объединяют функции на них. Реализация по умолчанию - это просто цепочка функций, но вы можете переопределить ее, если создадите свой собственный класс, реализующий интерфейс Function.

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

Ответ 4

Хотя это отличный вопрос и уже есть проницательные ответы, но вы должны быть очень осторожны с добавлением метода по default к любым существующим интерфейсам.

И я беру это из Effective Java - Item 21: Разработка интерфейсов с потомками следующие слова:

Но не всегда возможно написать метод по умолчанию, который поддерживает все инварианты каждой мыслимой реализации.

Хотя добавление метода по умолчанию к существующему интерфейсу облегчает обратную совместимость, но основная цель добавления этого состояла в том, чтобы позволить клиентам упростить использование нового интерфейса без необходимости написания собственной реализации.