Java 8 методы по умолчанию в качестве признаков: безопасно?

Безопасно ли использовать методы по умолчанию в качестве плохой версии черт характера в Java 8?

Некоторые утверждают, что могут сделать pandas sad, если вы используете их только ради него, потому что это круто, но это не мое намерение. Также часто напоминают о том, что были введены методы по умолчанию для поддержки эволюции API и обратной совместимости, что верно, но это не делает его неправильным или скрученным, чтобы использовать их как черты как таковые.

У меня следующий практический пример использования:

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

Или, возможно, определите a PeriodTrait:

public interface PeriodeTrait {
    Date getStartDate();
    Date getEndDate();
    default isValid(Date atDate) {
        ...
    }
}

Вполне возможно, что композиция может использоваться (или даже вспомогательные классы), но она кажется более многословной и захламленной и не позволяет использовать полиморфизм.

Итак, нормально ли/безопасно использовать методы по умолчанию в качестве основных признаков, или я должен беспокоиться о непредвиденных побочных эффектах?

Несколько вопросов относительно SO связаны с чертами Java vs Scala; это не главное. Я тоже не прошу просто высказаться. Вместо этого я ищу авторитетный ответ или, по крайней мере, проницательность для поля: если вы использовали методы по умолчанию как черты вашего корпоративного проекта, это оказалось временной бомбой?

Ответ 1

Короткий ответ: это безопасно, если вы используете их безопасно:)

Снежный ответ: скажите мне, что вы подразумеваете под чертами, и, может быть, я дам вам лучший ответ:)

Во всей серьезности термин "признак" не определен. Многие разработчики Java больше всего знакомы с чертами, так как они выражены в Scala, но Scala далек от первого языка, чтобы иметь черты, как по имени, так и по сути.

Например, в Scala, признаки имеют состояние (могут иметь переменные var); в Крепости они чистое поведение. Интерфейсы Java со стандартными методами не имеют состояния; означает ли это, что они не являются чертами? (Подсказка: это был трюк.)

Опять же, в Scala, черты формируются посредством линеаризации; если класс A расширяет черты X и Y, то порядок, в котором X и Y смешиваются, определяет, как разрешаются конфликты между X и Y. В Java этот механизм линеаризации отсутствует (он был частично отвергнут, потому что он был слишком "не-Java-подобным".)

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

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

Вот некоторые примеры использования, которые хорошо соответствуют целям дизайна:

  • Развитие интерфейса. Здесь мы добавляем новый метод к существующему интерфейсу, который имеет разумную реализацию по умолчанию с точки зрения существующих методов этого интерфейса. Примером может быть добавление метода forEach к Collection, где реализация по умолчанию записывается в терминах метода iterator().

  • "Необязательные" методы. Здесь разработчик интерфейса говорит: "Разработчики не должны реализовывать этот метод, если они хотят жить с ограничениями в функциональности, которые влекут за собой". Например, Iterator.remove получил значение по умолчанию, которое выдает UnsupportedOperationException; так как подавляющее большинство реализаций Iterator имеют такое поведение в любом случае, значение по умолчанию делает этот метод по существу необязательным. (Если поведение из AbstractCollection было выражено как значения по умолчанию на Collection, мы могли бы сделать то же самое для мутативных методов.)

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

  • Комбинаторы. Это композиционные методы, которые создают новые экземпляры интерфейса на основе текущего экземпляра. Например, методы Predicate.and() или Comparator.thenComparing() являются примерами комбинаторов.

Если вы предоставили реализацию по умолчанию, вы также должны указать некоторую спецификацию по умолчанию (в JDK мы используем тег @implSpec javadoc для этого), чтобы помочь разработчикам понять, хотят ли они переопределить метод или нет. Некоторые значения по умолчанию, такие как методы удобства и комбинаторы, почти никогда не переопределяются; другие, как необязательные методы, часто переопределяются. Вам нужно предоставить достаточную спецификацию (а не только документацию) о том, что делать по умолчанию promises, поэтому разработчик может принять разумное решение о необходимости ее переопределения.