Методы интерфейса Java по умолчанию конкретные варианты использования

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

в Scala методы внутри traits довольно полезны. Однако у Scala другой подход к лечению traits, чем Java с default методы. Думаю на несколько разрешение наследования или использование traitкак mixins.

кроме вышеуказанного использования, которые являются реальными сценариями, в которых используется default методы стоит? За эти годы возникла какая-то закономерность, которая их использует? Какие проблемы я могу решить с помощью таких методов?

5 ответов


Брайан Гетц и я освещали некоторые из этого на нашем JavaOne 2015 talk,дизайн API с Java 8 лямбда и потоками. несмотря на название, в конце есть некоторые материалы о методах по умолчанию.

слайды:https://stuartmarks.files.wordpress.com/2015/10/con6851-api-design-v2.pdf

видео:https://youtu.be/o10ETyiNIsM?t=24m

я подытожу здесь то, что мы сказали о дефолте методы.

Эволюция Интерфейса

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

есть несколько других случаев, хотя.

Дополнительные Способы

иногда методы интерфейса логически "необязательны". Рассмотрим методы мутатора для неизменяемых коллекций, например. Конечно, реализация требуется, но обычно в таких случаях она будет создавать исключение. Это можно легко сделать с помощью метода по умолчанию. Реализации могут наследовать метод исключения, если они не хотят его предоставлять, или могут переопределить его, если они хотят предоставить реализация. Пример: Iterator.remove.

Методы

иногда метод предоставляется для удобства вызывающих абонентов, и существует очевидная и оптимальная реализация. Эта реализация может быть предоставлена методом по умолчанию. Для реализации законно переопределять значение по умолчанию, но обычно нет причин, поэтому реализации обычно наследуют его. Примеры:Comparator.reversed, Spliterator.getExactSizeIfKnown, Spliterator.hasCharacteristics. Обратите внимание, что Spliterator был представлен в Java 8, включая методы по умолчанию, поэтому это явно не было случаем эволюции интерфейса.

простая реализация, предназначенная для переопределения

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

пример: List.sort. Реализация по умолчанию копирует элементы списка во временный массив, сортирует массив и копирует элементы обратно в список. Это правильная реализация, и иногда она не может быть улучшена (например, для LinkedList). Однако,ArrayList переопределяет sort и сортирует свой внутренний массив на месте. Это позволяет избежать накладных расходов на копирование.

теперь, очевидно sort был модернизирован на List и ArrayList в Java 8, поэтому эволюция не произошла таким образом. Но вы легко можете себе представить, как воспитывать нового List реализация. Вы, вероятно, изначально унаследуете sort реализация по умолчанию, пока вы получаете основы реализовано должным образом. Позже вы можете рассмотреть возможность реализации настраиваемого алгоритма сортировки, настроенного на внутреннюю организацию данных вашей новой реализации.


Первое, что приходит на ум, это использование методов по умолчанию для поддержки некоторых методов функционального программирования:

@FunctionalInterface
public interface Function3<A, B, C, D> {

    D apply(A a, B b, C c);

    default Function<A, Function<B, Function<C, D>>> curry() {
        return a -> b -> c -> this.apply(a, b, c);
    }

    default Function<B, Function<C, D>> bindFirst(A a) {
        return b -> c -> this.apply(a, b, c);
    }
}

пример использования:

Function3<Long, Long, Long, Long> sum = (a, b, c) -> a + b + c;
long result = sum.apply(1L, 2L, 3L); // 6

Function<Long, Function<Long, Function<Long, Long>>> curriedSum = sum.curry();
result = curriedSum.apply(1L).apply(2L).apply(3L); // 6

Function<Long, Function<Long, Long>> incr = sum.bindFirst(1L);
result = incr.apply(7L).apply(3L); // 11
result = incr.apply(6L).apply(7L); // 14

вы можете иметь аналогичные методы привязки для других параметров, реализованных с методами по умолчанию, такими как bindSecond и bindThird.

вы можете использовать методы по умолчанию для украшения родительского интерфейса (как объясняет @holi-java в своем ответе), также есть много примеров шаблона адаптера (Карри и привязка на самом деле адаптеры).


помимо функционального программирования, вы можете использовать стандартные методы для поддержки вид, ограниченное множественное наследование:

public interface Animal {

    String getHabitat();
}

public interface AquaticAnimal extends Animal {

    @Override
    default String getHabitat() {
        return "water";
    }
}

public interface LandAnimal extends Animal {

    @Override
    default String getHabitat() {
        return "ground";
    }
}

public class Frog implements AquaticAnimal, LandAnimal {

    private int ageInDays;

    public Frog(int ageInDays) {
        this.ageInDays = ageInDays;
    }

    public void liveOneDay() {
        this.ageInDays++;
    }

    @Override
    public String getHabitat() {
        if (this.ageInDays < 30) { // is it a tadpole?
            return AquaticAnimal.super.getHabitat();
        } // else
        return LandAnimal.super.getHabitat();
    }
}

пример:

Frog frog = new Frog(29);

String habitatWhenYoung = frog.getHabitat(); // water

frog.liveOneDay();
String habitatWhenOld = frog.getHabitat(); // ground

может быть, не лучший пример, но вы поняли идею...


другое использование было бы чертами:

public interface WithLog {

    default Logger logger() {
        return LoggerFactory.getLogger(this.getClass());
    }
}

public interface WithMetrics {

    default MetricsService metrics() {
        return MetricsServiceFactory.getMetricsService(
            Configuration.getMetricsIP(
                Environment.getActiveEnv())); // DEV or PROD
    }
}

теперь, когда у вас есть класс, который должен что-то регистрировать и сообщать о некоторых метриках, вы можете использовать:

public class YourClass implements WithLog, WithMetrics {

    public void someLongMethod() {

        this.logger().info("Starting long method execution...");

        long start = System.nanoTime();

        // do some very long action

        long end = System.nanoTime();

        this.logger().info("Finished long method execution");

        this.metrics().reportExecutionTime("Long method: ", end - start);
    }
}

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


Ну, у меня есть сценарий реального мира, в котором я их использовал. Вот контекст: я получаю результат от google maps api (путем предоставления широты и долготы) в виде Array результатов, это выглядит так:

GeocodingResult[] result

этот результат содержит некоторую информацию, которая мне нужна, например zip-code или locality или country. Разные услуги разные части этого ответа. Синтаксический анализ этого массива одинаков - вам просто нужно искать разные части.

поэтому я определил это в default метод внутри interface:

default Optional<String> parseResult(
        GeocodingResult[] geocodingResults, 
        AddressComponentType componentType,// enum
        AddressType addressType) { // enum

     ... Some parsing functionality that returns
      city, address or zip-code, etc
}

теперь в реализации интерфейса я просто использую этот метод.

 class Example implements Interface {

      @Override
      public Optional<String> findZipCode(Double latitude, Double longitude) {
         LatLng latLng = new LatLng(latitude, longitude);
         return parseResult(latLng, 
             AddressComponentType.POSTAL_CODE, 
             AddressType.POSTAL_CODE);
      }


    .. other methods that use the same technique

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


украсьте цепочку интерфейса функции методами по умолчанию

Я хочу иногда цеплять @FunctionalInterface, и мы уже видели в функции имея методы по умолчанию для цепочки функции, e.g:compose, andThen чтобы сделать код более элегантных. и самое главное, мы можем использовать частичная функция позже, например:

Predicate<?> isManager = null;
Predicate<?> isMarried = null;

marriedManager = employeeStream().filter(isMarried.and(isManager));
unmarriedManager = employeeStream().filter(isMarried.negate().and(isManager));

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

when(myMock.myFunction(anyString()))
       .then(will(returnsFirstArg()).as(String.class).to(MyObject::new));

вот мой вчерашний ответ:Mockito returnsFirstArg () использовать. из-за Answer не имеет цепных методов, поэтому я представляю другой Answer тип AnswerPipeline обеспечить цепные методы.

AnswerPipeline класс!--14-->

interface AnswerPipeline<T> extends Answer<T> {

    static <R> AnswerPipeline<R> will(Answer<R> answer) {
        return answer::answer;
    }

    default <R> AnswerPipeline<R> as(Class<R> type) {
        return to(type::cast);
    }

    default <R> AnswerPipeline<R> to(Function<T, R> mapper) {
        return it -> mapper.apply(answer(it));
    }
}

удаление адаптера прослушивателя по умолчанию с помощью методов по умолчанию

иногда нам нужно ввести по умолчанию Adapter класс java.util.EventListener который имеет несколько событий, которые нужно вызвать, но нас интересуют только некоторые из событий. например: swing create each *Adapter класс для каждого *Listener.

недавно я обнаружил, что это очень полезно, когда мы объявляем слушателей с методами по умолчанию, мы можем удалить средний класс адаптера. для пример:

interface WindowListener extends EventListener {

    default void windowOpened(WindowEvent e) {/**/}

    default void windowClosing(WindowEvent e) {/**/}

    default void windowClosed(WindowEvent e) {/**/}
}