Ответ 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, поэтому разработчик может принять разумное решение о необходимости ее переопределения.