Чому в методах інтерфейсу Java 8 "остаточний" не дозволено?


335

Однією з найбільш корисних особливостей Java 8 є нові defaultметоди інтерфейсів. По суті є дві причини (можуть бути й інші), чому вони були введені:

  • Надання фактичних реалізацій за замовчуванням. Приклад:Iterator.remove()
  • Дозвіл еволюції API JDK Приклад:Iterable.forEach()

З точки зору дизайнера API, мені хотілося б використовувати інші модифікатори для методів інтерфейсу, наприклад final. Це було б корисно при додаванні зручних методів, запобігаючи «випадковим» переменам у класах реалізації:

interface Sender {

    // Convenience method to send an empty message
    default final void send() {
        send(null);
    }

    // Implementations should only implement this method
    void send(String message);
}

Вищезазначене вже є звичайною практикою, якщо це Senderбув клас:

abstract class Sender {

    // Convenience method to send an empty message
    final void send() {
        send(null);
    }

    // Implementations should only implement this method
    abstract void send(String message);
}

Зараз, defaultі finalочевидно суперечать ключовим словам, але саме ключове слово за замовчуванням не було б суворо необхідним , тому я припускаю, що це протиріччя навмисне, щоб відобразити тонкі відмінності між "методами класу з тілом" (просто методи) та "інтерфейсом методи з тілом " (методи за замовчуванням), тобто відмінності, які я ще не зрозумів.

У якийсь момент підтримка таких модифікаторів, як staticі finalінтерфейсні методи, ще не була повністю вивчена, посилаючись на Брайана Геца :

Інша частина полягає в тому, як далеко ми збираємось піти на підтримку інструментів побудови класів в інтерфейсах, таких як кінцеві методи, приватні методи, захищені методи, статичні методи тощо. Відповідь: ми ще не знаємо

З цього часу в кінці 2011 року, очевидно, staticбуло додано підтримку методів в інтерфейсах. Зрозуміло, що це додало великої цінності самим бібліотекам JDK, як, наприклад, у Comparator.comparing().

Питання:

У чому причина final(і також static final) ніколи не потрапляла в інтерфейси Java 8?


10
Вибачте за мокру ковдру, але єдиний спосіб, коли на питання, висловлене в заголовку, можна буде відповісти в рамках ЗУ, - це цитатами Брайана Геца або Експертної групи JSR. Я розумію, що БГ запитувало публічне обговорення, але саме це суперечить умовам ПС, оскільки це "насамперед на основі думки". Мені здається, тут відповідальність покладається. Завдання Групи експертів та ширшого процесу спільноти Java - стимулювати дискусії та придумувати обґрунтування. Не так. Тому я голосую за закриття як "головним чином на основі думки".
Маркіз Лорнський

2
Як ми всі знаємо, що finalзаважає методу не перекрити, і бачачи, як Ви ОБОВ'ЯЗКОВО перекривати методи, успадковані від інтерфейсів, я не розумію, чому було б сенсом зробити його остаточним. Якщо тільки це не означало, що метод є остаточним ПІСЛЯ перекриття його один раз. У цьому випадку, можливо, виникають труднощі? Якщо я не розумію цього права, будь ласка, дозвольте мені кинути. Цікаво здається
Вінс Емі

22
@EJP: "Якщо я можу це зробити, то ви можете і відповісти на власне запитання, яке, таким чином, не потрібно було б запитувати". Це стосується майже всіх питань на цьому форумі, правда? Я завжди міг гуляти якусь тему протягом 5 годин, а потім навчитися тому, що всі інші повинні навчитися однаково важко. Або ми чекаємо ще декількох хвилин, щоб хтось дав ще кращу відповідь, від якої кожен у майбутньому (включаючи 12 оновлених результатів та 8 зірок поки що) може отримати прибуток, оскільки ТАК так добре посилається на Google. Отже, так. Це питання може ідеально вписатись у форму Q&A SO.
Лукас Едер

5
@VinceEmigh " ... бачачи, як ОБОВ'ЯЗКОВО переосмислити методи, успадковані від інтерфейсів ... " Це не вірно в Java 8. Java 8 дозволяє реалізовувати методи в інтерфейсах, це означає, що вам не доведеться їх реалізовувати під час реалізації заняття. Тут finalбуло б користь у запобіганні впровадженню класів над переосмисленням типової реалізації методу інтерфейсу.
awksp

28
@EJP ти ніколи не знаєш: Брайан Гец може відповісти !
assylias

Відповіді:


419

Це питання певною мірою пов'язане з тим, що є причиною того, що "синхронізовано" в методах інтерфейсу Java 8 не дозволено?

Основне, що потрібно розуміти про методи за замовчуванням, - це те, що головна мета дизайну - це розвиток інтерфейсу , а не "перетворення інтерфейсів у (посередні) риси". Хоча між ними є певне перекриття, і ми намагалися пристосувати останнє там, де це не перешкоджало колишньому, ці питання найкраще зрозуміти, якщо дивитися в цьому світлі. (Зауважимо також , що методи класу є відрізнятиметься від методів інтерфейсу, незалежно від того , що намір, в силу того , що методи інтерфейсу можна помножити успадкували.)

Основна ідея методу за замовчуванням: це метод інтерфейсу з реалізацією за замовчуванням, а похідний клас може забезпечити більш конкретну реалізацію. Оскільки центр дизайну розвивався інтерфейсом, це було вирішальною метою проекту, щоб методи за замовчуванням могли бути додані до інтерфейсів після факту, сумісними з джерелами та бінарними.

Занадто проста відповідь "чому не остаточні методи за замовчуванням" полягає в тому, що тоді тіло не буде просто реалізацією за замовчуванням, це було б єдиною реалізацією. Хоча це занадто проста відповідь, вона дає нам зрозуміти, що питання вже йде в сумнівному напрямку.

Ще одна причина, чому кінцеві методи інтерфейсу сумнівні - це те, що вони створюють неможливі проблеми для виконавців. Наприклад, припустимо, що у вас є:

interface A { 
    default void foo() { ... }
}

interface B { 
}

class C implements A, B { 
}

Тут все добре; Cуспадковує foo()від A. Тепер припустимо, що Bвін змінений на fooметод із замовчуванням:

interface B { 
    default void foo() { ... }
}

Тепер, коли ми переходимо до перекомпіляції C, компілятор скаже нам, що він не знає, для якої поведінки наслідувати foo(), тому Cповинен її перекрити (і міг би вибрати делегування, A.super.foo()якщо хотів би зберегти ту саму поведінку.) Але що робити Bзробив свій дефолт finalі Aне знаходиться під контролем автора C? Тепер Cбезповоротно порушено; він не може компілювати без переосмислення foo(), але він не може замінити, foo()якщо він був остаточним в B.

Це лише один приклад, але справа в тому, що остаточність методів - це справді інструмент, який має більше сенсу у світі класів однонаспадкування (як правило, яка пара визначає поведінку), ніж інтерфейси, які просто сприяють поведінці та можуть розмножуватися. передається у спадок. Дуже важко міркувати про те, "які інші інтерфейси можуть бути змішані з можливим реалізатором", і якщо метод інтерфейсу буде остаточним, це, ймовірно, спричинить ці проблеми (і вони підірвуться не людині, яка написала інтерфейс, а поганий користувач, який намагається це реалізувати.)

Ще одна причина заборонити їх - це те, що вони не означали б, що ви думаєте, що вони означають. Реалізація за замовчуванням розглядається лише у тому випадку, якщо клас (або його суперкласи) не дають декларації (конкретного чи абстрактного) методу. Якщо метод за замовчуванням був остаточним, але суперклас вже реалізував метод, він буде ігноруватися, що, мабуть, не те, чого автор за замовчуванням очікував, оголосивши його остаточним. (Ця поведінка успадкування є відображенням центру дизайну методів за замовчуванням - еволюція інтерфейсу. Слід мати можливість додати метод за замовчуванням (або реалізацію за замовчуванням до існуючого методу інтерфейсу) до існуючих інтерфейсів, які вже мають реалізацію, не змінюючи поведінка існуючих класів, які реалізують інтерфейс,


88
Фантастично бачити, як ви відповідаєте на питання про нові мовні особливості! Дуже корисно отримати роз’яснення щодо намірів та деталей дизайну, коли з’ясуємо, як нам слід використовувати нові функції. Чи інші люди, які брали участь у розробці проекту, вносять свій внесок у SO - чи ви це просто робите самі? Я буду стежити за вашими відповідями під тегом java-8 - я хотів би знати, чи є інші люди, які роблять те саме, і я можу слідувати за ними.
Шорн

10
@Shorn Stuart Marks був активним у тезі java-8. Джеремі Менсон публікував у минулому. Я також пам’ятаю, як бачив повідомлення Джошуа Блоха, але зараз не можу їх знайти.
Ассілія

1
Вітаємо вас за те, що придумали методи інтерфейсу за замовчуванням як набагато більш елегантний спосіб досягти того, що робить C # зі своїми непродуманими та досить нечестиво реалізованими методами розширення. Що стосується цього питання, то відповідь про неможливість вирішення конфлікту іменного роду вирішує питання, але решта наведених причин були непереконливою філологією. (Якщо я хочу, щоб метод інтерфейсу був остаточним, тоді ви повинні припустити, що у мене повинні бути свої досить прокляті вагомі причини для того, щоб я заборонив комусь надавати будь-яку реалізацію, відмінну від моєї.)
Майк Накіс

1
Однак вирішення конфлікту імен можна було досягти таким же чином, як це робиться в C #, додавши ім'я конкретного інтерфейсу, що реалізується, до декларації способу реалізації. Отже, з цього я беруся додому, що методи інтерфейсу за замовчуванням не можуть бути остаточними в Java, оскільки це вимагало б додаткових модифікацій синтаксису, до яких ви не відчували себе добре. (Занадто с-гострий, можливо?)
Майк Накіс

18
@ Спробування абстрактних класів досі є єдиним способом ввести стан або реалізувати основні методи Об'єкта. Методи за замовчуванням призначені для чистої поведінки ; абстрактні класи призначені для поведінки в поєднанні зі станом.
Брайан Гец

43

У списку розсилки лямбда про це багато дискусій . Один із тих, що, здається, містить багато дискусій щодо всього цього, є наступним: На видимість методу Різноманітний інтерфейс (був Фінал захисників) .

У цій дискусії, Талден, автор оригінального запитання задає щось дуже схоже на ваше запитання:

Рішення оприлюднити всіх учасників інтерфейсу було справді невдалим рішенням. Те, що будь-яке використання інтерфейсу у внутрішньому дизайні розкриває приватні деталі реалізації, є великим.

Це важко виправити, не додаючи до мови якихось незрозумілих чи сумісних порушень нюансів. Порушення сумісності такої величини та потенційної тонкості було б незрозумілим, тому повинно існувати рішення, яке не порушує існуючий код.

Не вдалося б повторно ввести ключове слово "пакунок" як специфікатор доступу. Відсутність специфікатора в інтерфейсі означатиме загальнодоступний доступ, а відсутність специфікатора в класі передбачає пакетний доступ. Які специфікатори мають сенс в інтерфейсі незрозуміло - особливо, якщо для мінімізації навантаження на знання розробників ми повинні переконатися, що специфікатори доступу означають те саме, що і в класі, і в інтерфейсі, якщо вони є.

За відсутності методів за замовчуванням я б припускав, що специфікатор члена в інтерфейсі повинен бути принаймні таким же видимим, як і сам інтерфейс (тому інтерфейс може бути реалізований у всіх видимих ​​контекстах) - з методами за замовчуванням, що це не так такий певний.

Чи було якесь чітке повідомлення щодо того, чи це навіть можлива всебічна дискусія? Якщо ні, чи слід це проводити в іншому місці.

Врешті відповідь Брайана Геца :

Так, це вже вивчається.

Однак дозвольте мені встановити деякі реалістичні очікування - особливості мови / VM мають тривалий час, навіть подібні до банальних. Час запропонування нових мовних ідей для Java SE 8 майже минув.

Тож, швидше за все, він ніколи не був реалізований, оскільки він ніколи не входив до сфери застосування. Це ніколи не було запропоновано вчасно розглянути.

В іншій гарячій дискусії про остаточні методи захисника з цього питання Брайан знову сказав :

І ви отримали саме те, що хотіли. Саме це додає ця особливість - багаторазове успадкування поведінки. Звичайно, ми розуміємо, що люди будуть використовувати їх як риси. І ми наполегливо працювали над тим, щоб запропонована ними модель успадкування була простою та чистою, щоб люди могли отримати хороші результати, роблячи це в найрізноманітніших ситуаціях. У той же час ми вирішили не виштовхувати їх за межі того, що працює просто і чисто, і це призводить до реакції "ух, ти не пішов досить далеко" у деяких випадках. Але насправді більшість цієї нитки, здається, бурчить про те, що келих наповнений лише на 98%. Я візьму ці 98% і продовжую!

Тож це підкріплює мою теорію про те, що вона просто не входила в сферу застосування та не була їхньою конструкцією. Вони зробили достатню функціональність для вирішення питань розвитку API.


4
Я бачу, що я повинен був включити стару назву, "захисники методів", у свою гучну одісею сьогодні вранці. +1, щоб викопати це.
Marco13

1
Приємно перекопувати історичні факти. Ваші висновки добре узгоджуються з офіційною відповіддю
Лукас Едер

Я не бачу, чому це порушить зворотну сумісність. фінал раніше не дозволявся. тепер вони дозволяють приватні, але не захищені. myeh ... private не може реалізувати ... інтерфейс, що розширює інший інтерфейс, може реалізувати частини батьківського інтерфейсу, але він повинен піддавати можливості перевантаження іншим ...
mmm

17

Буде важко знайти та визначити відповідь "ТО", бо резони, згадані в коментарях від @EJP: У світі є приблизно 2 (+/- 2) людей, які взагалі можуть дати точну відповідь . І сумнівається, що відповідь може бути чимось на кшталт "Підтримка остаточних методів за замовчуванням, здавалося, не вартує зусиль щодо реструктуризації внутрішніх механізмів вирішення дзвінків". Це, звичайно, міркування, але це, принаймні, підкріплене тонкими доказами, як ця Заява (однією з двох осіб) у списку розсилки OpenJDK :

"Я вважаю, що якщо були дозволені методи" остаточного за замовчуванням ", їм може знадобитися перезапис з внутрішнього виклику, зокрема до інтерфейсу, що бачить користувач."

і тривіальні факти, такі як метод просто не вважається (дійсно) остаточним методом, коли це defaultметод, як це реалізовано в даний час у методі :: is_final_method в OpenJDK.

Подальшу справді "авторську" інформацію насправді важко знайти навіть при надмірних веб-пошуках та читанні журналів фіксації. Я подумав, що це може бути пов’язано з потенційними двозначностями під час вирішення викликів методу інтерфейсу з викликами методу invokeinterfaceінструкцій та класу, що відповідає invokevirtualінструкції: Для invokevirtualінструкції може бути простий пошук в Vtable , оскільки метод повинен бути успадкований з суперкласу, або реалізований класом безпосередньо. На відміну від цього, invokeinterfaceвиклик повинен вивчити відповідний сайт виклику, щоб з’ясувати, на який інтерфейс цей виклик насправді посилається (це більш детально пояснено на сторінці InterfaceCalls на HotSpot Wiki). Однак,final методах, або не вставляються у vtableвзагалі, або замінити існуючі записи в таблиці віртуальних (див klassVtable.cpp. Лінія 333 ), а так же, по замовчуванням методи заміна існуючих записів в таблиці віртуального (див klassVtable.cpp, лінія 202 ). Тож справжня причина (і, отже, відповідь) повинна ховатися глибше всередині (досить складного) механізму вирішення викликів методу, але, можливо, ці посилання все-таки будуть вважатися корисними, будь то лише для інших, хто зуміє отримати фактичну відповідь з того.


Дякуємо за цікаве розуміння. П'єса Джона Роза - цікавий слід. Я все ще не згоден з @EJP. Як зустрічний приклад, ознайомтеся з моєю відповіддю на дуже цікаве, дуже схоже на стилі запитання Пітера Лодрі . Це є можливість викопати історичні факти, і я завжди радий знайти їх тут на переповнення стека (де ж ще?). Звичайно, ваша відповідь все ще спекулятивна, і я не на 100% переконаний, що деталі щодо впровадження JVM будуть остаточною причиною (каламбур), щоб так чи інакше записати JLS ...
Лукаш Едер,

@LukasEder Звичайно, ці види питань є цікавими і IMHO вписуватися в шаблон Q & A. Я думаю, що тут виникли два суттєві моменти, які спричинили суперечку: Перше - це те, що ви попросили "причину". У багатьох випадках це може бути офіційно не зафіксовано. Наприклад , немає «причина» згадується в JLS , чому немає беззнакових ints, але бачити stackoverflow.com/questions/430346 ... ...
Marco13

... ... Друге - ви запитували лише "авторські цитати", що зменшує кількість людей, які наважуються написати відповідь з "кількох десятків" до ... "приблизно нуля". Крім того, я не впевнений у тому, як переплітаються розвиток JVM та написання JLS, тобто в тому, наскільки розвиток впливає на те, що написано в JLS, але ... я не уникну тут будь-яких спекуляцій; -)
Marco13

1
Я все ще відпочиваю своєю справою. Дивіться , хто відповів на мій інше питання :-) Це тепер назавжди буде ясно з авторитетним відповіддю, тут на переповнення стека , чому було прийнято рішення не підтримувати synchronizedна defaultметодах.
Лукас Едер

3
@ LukasEder Я бачу, те саме тут. Хто міг цього очікувати? Причини досить переконливі, особливо для цього finalпитання, і дещо принизливо, що ніхто інший, здавалося, не думав про подібні приклади (або, можливо, дехто замислювався над цими прикладами, але не вважав достатньо авторитетним, щоб відповісти). Тож тепер (вибачте, я маю це зробити:) останнє слово вимовляється.
Marco13

4

Я б не вважав, що потрібно вказувати finalметод інтерфейсу convienience, але я можу погодитися, що це може бути корисним, але, здавалося б, витрати переважають переваги.

У будь-якому випадку, що ви повинні зробити, це написати правильний javadoc для методу за замовчуванням, показуючи, що саме є методом, а це не дозволяється. Таким чином класи, що реалізують інтерфейс, "не дозволяють" змінювати реалізацію, хоча гарантій немає.

Будь-хто може написати текст, Collectionякий дотримується інтерфейсу, а потім виконує методи, абсолютно протилежні інтуїтивним, немає ніякого способу захистити себе від цього, крім написання обширних одиничних тестів.


2
Контракти Javadoc є дійсним вирішенням конкретного прикладу, який я перелічив у своєму запитанні, але питання справді не стосується зручності використання методу інтерфейсу. Питання полягає в авторитетній причині, чому finalне було дозволено використовувати interfaceметоди Java 8 . Недостатнє співвідношення витрат і вигод - хороший кандидат, але поки що це спекуляція.
Лукас Едер

0

Ми додаємо defaultключове слово до нашого методу всередині, interfaceколи ми знаємо, що клас, який розширює нашу interfaceабо не може виконати overrideнашу реалізацію. Але що робити, якщо ми хочемо додати метод, який ми не хочемо, щоб якийсь клас-реалізатор переосмислював? Ну, нам були доступні два варіанти:

  1. Додайте default finalметод.
  2. Додати а staticметод.

Тепер Java каже, що якщо у нас є classдва або більше interfacesтаких, що мають defaultметод з точно такою ж назвою методу та підписом, тобто вони дублюються, то нам потрібно забезпечити реалізацію цього методу в нашому класі. Тепер у випадку default finalметодів ми не можемо забезпечити реалізацію, і ми застрягли. І тому finalключове слово не використовується в інтерфейсах.

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