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

116

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

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

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

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

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

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

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

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

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

Youri
источник
Мне кажется, что вы можете получить те же преимущества от наследования абстрактного класса и не беспокоиться о том, чтобы заставить панд плакать ... Единственная причина, по которой я могу использовать метод по умолчанию в интерфейсе, заключается в том, что вам нужна функциональность и вы не можете изменить кучу устаревшего кода, основанного на интерфейсе.
Девен Филлипс
1
Я согласен с @ infosec812 о расширении абстрактного класса, который определяет собственное поле статического регистратора. Разве ваш метод logger () не будет создавать новый экземпляр регистратора каждый раз при его вызове?
Eidan Spiegel
Для ведения журнала вы можете посмотреть Projectlombok.org и его аннотацию @ Slf4j.
Девен Филлипс,

Ответы:

120

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

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

Если серьезно, то термин «черта» не имеет четкого определения. Многие Java-разработчики больше всего знакомы с чертами, выраженными в Scala, но Scala - далеко не первый язык, имеющий черты, как по названию, так и по сути.

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

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

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

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

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

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

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

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

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

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

Брайан Гетц
источник
9
Спасибо, Брайан, за исчерпывающий ответ. Теперь я могу использовать методы по умолчанию с легким сердцем. Читатели: дополнительную информацию от Брайана Гетца об эволюции интерфейса и методах по умолчанию можно найти, например, в NightHacking Worldwide Lambdas .
youri
Спасибо @ brian-goetz. Из того, что вы сказали, я думаю, что методы Java по умолчанию ближе к понятию традиционных черт, как это определено в статье Дюкасса и др. ( Scg.unibe.ch/archive/papers/Duca06bTOPLASTraits.pdf ). «Черты» Scala вообще не кажутся мне чертами, поскольку у них есть состояние, они используют линеаризацию в своей композиции, и, похоже, они также неявно разрешают конфликты методов - и это все то, что традиционные черты не делают. иметь. Фактически, я бы сказал, что трейты Scala больше похожи на миксины, чем на трейты. Что вы думаете? PS: Я никогда не писал на Scala.
adino
Как насчет использования пустых реализаций по умолчанию для методов в интерфейсе, который действует как слушатель? Реализация слушателя может быть заинтересована только в прослушивании нескольких методов интерфейса, поэтому, сделав методы по умолчанию, разработчик должен реализовать только те методы, которые ему нужно слушать.
Лахиру Чандима
1
@LahiruChandima Как с MouseListener? В той степени, в которой этот стиль API имеет смысл, он укладывается в корзину «необязательных методов». Обязательно четко задокументируйте необязательность!
Брайан Гетц
Да. Так же, как в MouseListener. Спасибо за ответ.
Лахиру Чандима