Почему «final» не допускается в интерфейсных методах Java 8?

335

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

  • Предоставление фактических реализаций по умолчанию. Пример:Iterator.remove()
  • С учетом эволюции JDK API. Пример: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
Я извиняюсь за то, что был мокрым, но единственный способ получить ответ на вопрос, указанный в названии, в рамках условий SO - это цитата Брайана Гетца или группы экспертов JSR. Я понимаю, что BG потребовала публичного обсуждения, но это именно то, что противоречит условиям SO, поскольку она «в основном основана на мнении». Мне кажется, что здесь уклоняются обязанности. Задача экспертной группы и более широкого процесса сообщества Java - стимулировать обсуждение и выдвигать обоснования. Не ТАК. Так что я голосую за закрытие как «в первую очередь основанное на мнении».
Маркиз Лорн
2
Как мы все знаем, finalпредотвращает переопределение метода, и, видя, как вы ДОЛЖНЫ переопределять методы, унаследованные от интерфейсов, я не понимаю, почему имеет смысл сделать его окончательным. Если только это не означало, что метод является окончательным ПОСЛЕ переопределения его один раз .. В таком случае, может быть, есть трудности? Если я не понимаю этого права, пожалуйста, дайте мне знать. Кажется интересным
Винс Эми
22
@EJP: «Если я смогу это сделать, то и вы сможете ответить на свой собственный вопрос, который поэтому не нужно было бы задавать». Это относится почти ко всем вопросам на этом форуме, верно? Я всегда мог в течение 5 часов гуглить какую-то тему, а потом узнавать что-то, что все остальные должны выучить таким же сложным способом. Или мы ждем еще пару минут, чтобы кто-нибудь дал еще лучший ответ, на который каждый в будущем (включая 12 голосов и 8 звезд) может получить прибыль, потому что на SO так хорошо ссылаются в Google. Так да. Этот вопрос может отлично вписаться в Q & A форме, в.
Лукас Эдер
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. Джереми Мэнсон написал в прошлом. Я также помню сообщения от Джошуа Блоха, но не могу найти их сейчас.
assylias
1
Поздравляем с тем, что вы выбрали методы интерфейса по умолчанию как гораздо более элегантный способ выполнения того, что делает C # с помощью своих непродуманных и довольно уродливых методов расширения. Что касается этого вопроса, то ответ о невозможности разрешения конфликта имен разрешает проблему, но остальные причины были неубедительными. (Если я хочу, чтобы интерфейсный метод был окончательным, то вы должны предположить, что у меня должны быть свои собственные чертовски веские основания для того, чтобы запретить кому-либо предоставлять какую-либо реализацию, отличную от моей.)
Майк Накис,
1
Тем не менее, разрешение конфликтов имен могло быть достигнуто способом, аналогичным тому, как это делается в C #, путем добавления имени конкретного реализуемого интерфейса к объявлению метода реализации. Итак, что я извлекаю из всего этого, так это то, что методы интерфейса по умолчанию не могут быть окончательными в Java, потому что это потребовало бы дополнительных изменений в синтаксисе, что вас не устраивало. (Возможно, слишком резкий?)
Майк Накис
18
@ Попытка Абстрактные классы по-прежнему являются единственным способом введения состояния или реализации основных методов Object. Методы по умолчанию для чистого поведения ; абстрактные классы для поведения в сочетании с состоянием.
Брайан Гетц
43

В лямбда-рассылке много дискуссий по этому поводу . Одна из тех, которые, кажется, содержат много дискуссий обо всем этом, заключается в следующем: Видимость метода интерфейса Varied (была Final defenders) .

В этой дискуссии Талден, автор оригинального вопроса, задает нечто очень похожее на ваш вопрос:

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

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

Может быть жизнеспособным повторное введение ключевого слова package в качестве спецификатора доступа. Отсутствие спецификатора в интерфейсе подразумевает открытый доступ, а отсутствие спецификатора в классе подразумевает пакетный доступ. Какие спецификаторы имеют смысл в интерфейсе, неясно - особенно если, чтобы минимизировать нагрузку на разработчиков, мы должны убедиться, что спецификаторы доступа означают одно и то же в классе и интерфейсе, если они присутствуют.

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

Были ли какие-либо четкие сообщения о том, является ли это возможным обсуждением в рамках всей области? Если нет, то должно ли оно быть проведено в другом месте.

В конце концов ответ Брайана Гетца был:

Да, это уже изучается.

Тем не менее, позвольте мне установить некоторые реалистичные ожидания - функции языка / виртуальной машины имеют длительное время выполнения, даже кажущиеся тривиальными. Время для предложения новых возможностей языка Java Java 8 прошло.

Так что, скорее всего, он никогда не был реализован, потому что он никогда не был частью области видимости. Это никогда не было предложено вовремя, чтобы быть рассмотренным.

В другом бурном обсуждении окончательных методов защиты на эту тему Брайан снова сказал :

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

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

Эдвин Далорсо
источник
4
Я вижу, что я должен был включить старое имя, "методы защитника", в мою одиссею по поиску сегодня утром. +1 за то, что выкопал это.
Marco13
1
Приятно выкапывать исторические факты. Ваши выводы хорошо совпадают с официальным ответом
Лукас Эдер,
Я не понимаю, почему это нарушит обратную совместимость. финал не был разрешен раньше. теперь они позволяют частным, но не защищены. myeh ... private не может реализовать ... интерфейс, расширяющий другой интерфейс, может реализовывать части родительского интерфейса, но он должен предоставлять возможность перегрузки другим ...
ммм
17

Будет трудно найти и определить ответ «THE», потому что резонаторы, упомянутые в комментариях @EJP: в мире есть примерно 2 (+/- 2) человека, которые могут дать определенный ответ вообще . И в сомнении ответ мог бы быть что-то вроде: «Поддержка окончательных методов по умолчанию не стоила усилий по реструктуризации внутренних механизмов разрешения вызовов». Конечно, это предположение, но оно, по крайней мере, подкреплено неуловимыми доказательствами, такими как это заявление (одного из двух) в списке рассылки OpenJDK :

«Полагаю, если методы« окончательного по умолчанию »были разрешены, им, возможно, потребуется переписать внутреннюю invokespecial на видимый пользователем invokeinterface».

и тривиальные факты, как этот метод просто не считается (действительно) конечным методом, когда он является defaultметодом, как в настоящее время реализовано в Method :: is_final_method в OpenJDK.

Дальнейшую действительно «авторитетную» информацию действительно трудно найти, даже с чрезмерными поисками в сети и чтением журналов коммитов. Я подумал, что это может быть связано с потенциальной неоднозначностью при разрешении вызовов метода интерфейса с помощью invokeinterfaceинструкций и вызовов методов класса, соответствующих invokevirtualинструкции: Для invokevirtualинструкции может быть простой поиск в vtable , поскольку метод должен быть либо наследуемым из суперкласса, или реализуется классом напрямую. В отличие от этого, invokeinterfaceвызов должен изучить соответствующий сайт вызова, чтобы выяснить, к какому интерфейсу относится этот вызов (это объясняется более подробно в методах, которые либо не вставляются в виртуальную таблицу). странице InterfaceCalls в HotSpot Wiki). Тем не мение,final или заменить существующие записи в vtable (см. klassVtable.cpp. Строка 333 ) и аналогичным образом методы по умолчанию заменяют существующие записи в vtable (см. klassVtable.cpp, Строка 202 ). Так актуально причина (и, следовательно, ответ) должна быть скрыта глубже в (довольно сложных) механизмах разрешения вызовов методов, но, возможно, эти ссылки, тем не менее, будут рассматриваться как полезные, будь то только для тех, кому удастся получить фактический ответ из этого.

Marco13
источник
Спасибо за интересное понимание. Произведение Джона Роуза - интересный след. Я все еще не согласен с @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удобный метод интерфейса, хотя я могу согласиться, что он может быть полезным, но, по-видимому, затраты перевешивают преимущества.

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

Любой может написать a, Collectionкоторый придерживается интерфейса, а затем делает вещи в методах, которые абсолютно противоречат интуитивному принципу, от этого нет никакого способа защитить себя, кроме как писать обширные модульные тесты.

skiwi
источник
2
Контракты Javadoc - это верный обходной путь к конкретному примеру, который я перечислил в своем вопросе, но на самом деле вопрос не в сценарии использования метода удобного интерфейса. Вопрос об авторитетной причине, почему finalбыло решено не разрешать interfaceметоды Java 8 . Недостаточное соотношение затрат и выгод является хорошим кандидатом, но пока это предположение.
Лукас Эдер
0

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

  1. Добавьте default finalметод.
  2. Добавьте staticметод.

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

Ashutosh
источник