Методы Java 8 по умолчанию как черты: безопасно?
Безопасно ли использовать методы по умолчанию как черты бедного человека в 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 и 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 для этого), чтобы помочь разработчикам понять, хотят ли они переопределить метод или нет. Некоторые значения по умолчанию, такие как удобные методы и комбинаторы, почти никогда не отменяются; другие, как необязательные методы, часто переопределяются. Вы должны предоставить достаточно спецификации (а не только документацию) о том, что обещает сделать по умолчанию, чтобы разработчик мог принять разумное решение о том, нужно ли ему переопределять его.