Методи за замовчуванням 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 проти Scala; в цьому справа не в цьому. Я не прошу лише думок. Натомість я шукаю авторитетну відповідь або принаймні полезна огляд: якщо ви використовували методи за замовчуванням як риси у своєму корпоративному проекті, чи виявилось це тимчасовою задачею?


Мені здається, що ти можеш отримати однакові переваги від успадкування абстрактного класу і не потрібно турбуватися про те, щоб змусити панди плакати ... Єдина причина, яку я бачу використовувати метод за замовчуванням в інтерфейсі, це те, що вам потрібна функціональність і не можна змінити купу застарілого коду, який базується на інтерфейсі.
Девен Філліпс

1
Я погоджуюся з @ infosec812 щодо розширення абстрактного класу, який визначає власне поле статичного реєстратора. Чи не буде ваш метод logger () створювати новий екземпляр реєстратора кожного разу, коли він викликається?
Ейдан Шпігель

Для ведення журналів ви можете подивитися на Projectlombok.org та їх анотацію @ Slf4j.
Девен Філліпс

Відповіді:


120

Коротка відповідь: безпечно, якщо ви їх безпечно використовуєте :)

Спритна відповідь: скажіть, що ви маєте на увазі під рисами, і, можливо, я дам вам кращу відповідь :)

По суті, термін "риса" недостатньо визначений. Багато розробників 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 для цього), щоб допомогти інженерам зрозуміти, хочуть вони замінити метод чи ні. Деякі параметри за замовчуванням, як-от зручні методи та комбінатори, майже ніколи не змінюються; інші, як необов'язкові методи, часто перекриваються. Потрібно надати достатню специфікацію (не лише документацію) про те, що обіцяє зробити за замовчуванням, щоб виконавець міг прийняти обґрунтоване рішення про те, чи потрібно їм це перекривати.


9
Дякую Брайану за цю вичерпну відповідь. Тепер я можу використовувати світлини за замовчуванням із легким серцем. Читачі: більше інформації від Брайана Геца про еволюцію інтерфейсу та методи за замовчуванням можна знайти, наприклад, у NightHacking Worldwide Lambdas .
youri

Дякую @ brian-goetz. З того, що ви сказали, я думаю, що методи за замовчуванням Java ближче до поняття традиційних рис, визначених у документі Ducasse et al. ( Scg.unibe.ch/archive/papers/Duca06bTOPLASTraits.pdf ). "Риси" Скали для мене зовсім не схожі на риси, оскільки вони мають стан, використовують лінеаризацію у своєму складі, і, здається, вони також неявно вирішують конфлікти методів - і це все те, чого традиційні риси не мають мати. Насправді, я б сказав, риси Scala більше схожі на міксини, ніж на риси. Що ти думаєш? PS: Я ніколи не кодував Scala.
adino

Як щодо використання порожніх реалізацій за замовчуванням для методів в інтерфейсі, який виступає слухачем? Реалізація слухача може бути зацікавлена ​​у прослуховуванні лише кількох методів інтерфейсу, тому, роблячи методи за замовчуванням, реалізатор повинен реалізувати лише ті методи, які йому потрібно слухати.
Лахіру Чандіма

1
@LahiruChandima Як з MouseListener? Наскільки цей стиль API має сенс, це вписується у відро "необов'язкових методів". Обов’язково чітко документуйте необов'язковість!
Брайан Гец

Так. Так само, як і в MouseListener. Дякуємо за відповідь.
Лахіру Чандіма
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.