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

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

  • Предоставление фактических реализаций по умолчанию. Пример: Iterator.remove()
  • Разрешить эволюцию JDK API. Пример: Iterable.forEach()

С точки зрения дизайнера API мне бы хотелось использовать другие модификаторы для методов интерфейса, например final . Это было бы полезно при добавлении удобных методов, предотвращающих «случайные» переопределения при реализации classов:

 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 был classом:

 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 явно противоречат ключевым словам, но ключевое слово по умолчанию не было бы строго обязательным , поэтому я предполагаю, что это противоречие преднамеренное, чтобы отразить тонкие различия между «методами classа с телом» (только методы) и «методы интерфейса с телом» (методы по умолчанию), то есть различия, которые я еще не понял.

В какой-то момент поддержка модификаторов, таких как static и final методы интерфейса, еще не была полностью изучена, сославшись на Брайана Гетца :

Другая часть – это то, как далеко мы продвинемся для поддержки инструментов построения classов в интерфейсах, таких как конечные методы, частные методы, защищенные методы, статические методы и т. Д. Ответ: мы пока не знаем

С того времени в конце 2011 года, очевидно, была добавлена ​​поддержка static методов в интерфейсах. Очевидно, что это добавило большую ценность самим библиотекам JDK, например, Comparator.comparing() .

Вопрос:

В чем причина, почему final (а также static final ) никогда не попадал на интерфейсы Java 8?

Этот вопрос в какой-то степени связан с тем, в чем причина, по которой «синхронизация» не допускается в методах интерфейса Java 8?

Главное, чтобы понять методы по умолчанию, заключается в том, что основной целью проекта является эволюция интерфейса , а не «преrotation интерфейсов в (посредственные) черты». Хотя между ними есть некоторое совпадение, и мы пытались разместить последнее, где это не мешало первому, эти вопросы лучше всего понять, если смотреть на этот свет. (Также обратите внимание, что методы classа будут отличаться от методов интерфейса, независимо от того, в чем заключается намерение, в силу того, что методы интерфейса могут быть унаследованы.)

Основная идея метода по умолчанию: это метод интерфейса с реализацией по умолчанию, а производный class может обеспечить более конкретную реализацию. И поскольку дизайн-центр был эволюцией интерфейса, критическая цель дизайна заключалась в том, что методы по умолчанию могут быть добавлены к интерфейсам после факта в совместимом с исходным кодом и совместимом с двоичным кодом образом.

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

Еще одна причина, по которой окончательные методы интерфейса сомнительны, заключается в том, что они создают невозможные проблемы для разработчиков. Например, предположим, что у вас есть:

 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

Это всего лишь один пример, но дело в том, что окончательные методы – это действительно инструмент, который имеет больше смысла в мире classов с одним наследованием (как правило, это состояние пары для поведения), чем интерфейсы, которые просто способствуют поведению и могут быть многократно унаследованы , Слишком сложно рассуждать о том, «какие другие интерфейсы могут быть смешаны с потенциальным разработчиком», а разрешение метода интерфейса будет окончательным, вероятно, вызовет эти проблемы (и они взорвутся не на человека, который написал интерфейс, а на бедный пользователь, который пытается его реализовать.)

Еще одна причина их запретить – это не значит, что вы думаете. Реализация по умолчанию рассматривается только в том случае, если class (или его суперclassы) не предоставляет объявление (конкретное или абстрактное) метода. Если метод по умолчанию был окончательным, но суперclass уже реализовал метод, значение по умолчанию будет проигнорировано, что, вероятно, не то, что ожидал автор по умолчанию при объявлении его окончательным. (Это поведение наследования является reflectionм центра разработки для методов по умолчанию – эволюция интерфейса. Должно быть возможно добавить метод по умолчанию (или реализацию по умолчанию к существующему методу интерфейса) к существующим интерфейсам, которые уже имеют реализации, без изменения поведение существующих classов, которые реализуют интерфейс, гарантируя, что classы, которые уже работали до того, как были добавлены методы по умолчанию, будут работать одинаково при наличии методов по умолчанию.)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Я бы не подумал, что необходимо указать final метод интерфейса convienience, я могу согласиться, хотя это может быть полезно, но, по-видимому, издержки имеют избыточный вес.

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

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

  • Почему компилятор Scala запрещает перегруженные методы с аргументами по умолчанию?
  • Каков пакет по умолчанию, в который помещаются мои classы, если я не укажу его?
  • Force R не использовать экспоненциальную нотацию (например, e + 10)?
  • Как загрузить пакеты в R автоматически?
  • Объявление примитивов / объектов, значения инициализации по умолчанию
  • Создание (и доступ) разреженной матрицы с записями NA по умолчанию
  • Программный эквивалент по умолчанию (Тип)
  • Почему компилятор не может вывести тип шаблона из аргументов по умолчанию?
  • Значение по умолчанию для типа в Runtime
  • Поддерживает ли Java значения параметров по умолчанию?
  • Как вы используете конструктор, отличный от стандартного для члена?
  • Давайте будем гением компьютера.