Рубрики
Без рубрики

Программирование только для интерфейса на Java

Забавный, но полезный трюк с интерфейсами Java и монадами. С пометкой java, lang, для начинающих, учебник.

Комбинация методов по умолчанию в интерфейсе Java вместе с Шаблон Monad позволяет писать классы только для интерфейса. Там классы не имеют отдельных классов реализации, только интерфейсы. Ну, конечно, технически у них есть реализация, но эта реализация представляет собой крошечный анонимный онлайн-класс.

Я продемонстрирую эту идею, написав (незавершенную) реализацию Может быть, монада, иногда называемая Необязательной или Опцией . Этот контейнер используется для представления значений, которые могут присутствовать или отсутствовать. Например Map.get(ключ) может использовать этот контейнер для представления результата поиска. Если значение найдено, то Возвращается некоторое значение, в противном случае None (пустой контейнер) не возвращается.

В Java 8 есть Необязательный , который реализует такое поведение, поэтому мы можем посмотреть на его API и реализовать что-то подобное:

public interface Option {
     Option map(final Function mapper);

     Option flatMap(final Function> mapper);

    Option ifPresent(final Consumer consumer);

    Option ifEmpty(final Runnable consumer);

    T otherwise(final T replacement);
}

Если мы попытаемся реализовать этот интерфейс просто, мы должны создать класс, который будет содержать значение. Затем в каждом методе мы будем проверять, равно ли это значение null и если да, выполните одно действие, а если нет – выполните другое действие. Похоже на логику, повторяющую запах кода. Давайте извлекем эту логику в метод, который будет принимать две функции и будет вызывать их в зависимости от состояния Опции (т.е. значение присутствует или нет):

    U map(final Function presentMapper, final Supplier emptyMapper);

Теперь мы можем представить все остальные методы, используя этот новый метод map() :

    default  Option map(final Function mapper) {
        return Option.option(map(mapper, () -> null));
    }

    default  Option flatMap(final Function> mapper) {
        return map(mapper, Option::empty);
    }

    default Option ifPresent(final Consumer consumer) {
        map(v -> {consumer.accept(v); return null;}, () -> null);
        return this;
    }

    default Option ifEmpty(final Runnable consumer) {
        map(v -> v, () -> { consumer.run(); return null;} );
        return this;
    }

    default T otherwise(final T replacement) {
        return map(v -> v, () -> replacement);
    }

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

    static  Option option(final T value) {
        return (value == null) ? empty() : new Option() {
            @Override
            public  U map(final Function presentMapper, final Supplier emptyMapper) {
                return presentMapper.apply(value);
            }
        };
    }

    static  Option empty() {
        return new Option() {
            @Override
            public  U map(final Function presentMapper, final Supplier emptyMapper) {
                return emptyMapper.get();
            }
        };
    }

Вот и все, вся необходимая функциональность реализована внутри интерфейса.

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

  • Пустой экземпляр фактически не содержит значения, для него нет четного поля (в отличие от Java 8 Необязательно ). Мы можем оптимизировать реализацию и каждый раз возвращать один и тот же экземпляр для пустого контейнера.
  • Явной переменной экземпляра для сохраненного значения не существует, она неявно сохраняется компилятором Java при создании непустого экземпляра.
  • Отсутствие ветвления внутри кода должно помочь повысить производительность во время выполнения. В этом случае выигрыш будет незначительным, но этот метод является общим и может быть использован в тех случаях, когда выигрыш может быть значительным.

Оригинал: “https://dev.to/siy/interface-only-programming-in-java-h1m”