Почему "final" не разрешен в интерфейсных методах Java 8?
Одной из самых полезных функций 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?
4 ответа
Этот вопрос в некоторой степени связан с тем, что является причиной, по которой "синхронизация" не допускается в интерфейсных методах 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
,
Это только один пример, но суть в том, что конечные методы действительно представляют собой инструмент, который имеет больше смысла в мире классов с одним наследованием (как правило, которые связывают состояние с поведением), чем с интерфейсами, которые просто вносят свой вклад в поведение и могут наследоваться множественным образом., Слишком сложно рассуждать о том, "какие другие интерфейсы могут быть смешаны с возможным исполнителем", и, допустив, что интерфейсный метод будет окончательным, это может вызвать эти проблемы (и они могут взорваться не от человека, который написал интерфейс, а от бедный пользователь, который пытается это реализовать.)
Другая причина запретить им это то, что они не будут иметь в виду то, что вы думаете, они имеют в виду. Реализация по умолчанию рассматривается только в том случае, если класс (или его суперклассы) не предоставляют объявление (конкретное или абстрактное) метода. Если бы метод по умолчанию был финальным, но суперкласс уже реализовал этот метод, этот метод по умолчанию был бы проигнорирован, что, вероятно, не соответствует ожиданиям автора по умолчанию при объявлении его финальным. (Это поведение наследования является отражением центра разработки для методов по умолчанию - эволюция интерфейса. Должна быть возможность добавить метод по умолчанию (или реализацию по умолчанию к существующему методу интерфейса) к существующим интерфейсам, которые уже имеют реализации, без изменения поведение существующих классов, которые реализуют интерфейс, гарантируя, что классы, которые уже работали до добавления методов по умолчанию, будут работать так же при наличии методов по умолчанию.)
В лямбда-рассылке много дискуссий по этому поводу. Одна из тех, которые, кажется, содержат много дискуссий обо всем этом, заключается в следующем: видимость метода интерфейса Varied (была Final defenders).
В этой дискуссии Талден, автор оригинального вопроса, задает нечто очень похожее на ваш вопрос:
Решение обнародовать всех участников интерфейса было действительно неудачным решением. То, что любое использование интерфейса во внутреннем дизайне подвергает реализации частным деталям, является большим.
Это трудно исправить, не добавляя немного неясных или несовместимых нюансов в язык. Нарушение совместимости такого масштаба и потенциальной тонкости может показаться недобросовестным, поэтому должно существовать решение, которое не нарушает существующий код.
Может быть жизнеспособным повторное введение ключевого слова package в качестве спецификатора доступа. Отсутствие спецификатора в интерфейсе подразумевает открытый доступ, а отсутствие спецификатора в классе подразумевает пакетный доступ. Какие спецификаторы имеют смысл в интерфейсе, неясно - особенно если, чтобы минимизировать нагрузку на разработчиков, мы должны убедиться, что спецификаторы доступа означают одно и то же в классе и интерфейсе, если они присутствуют.
В отсутствие методов по умолчанию я бы предположил, что спецификатор члена в интерфейсе должен быть по крайней мере таким же видимым, как и сам интерфейс (так что интерфейс может быть реализован во всех видимых контекстах) - с методами по умолчанию, которые не являются так уверен.
Были ли какие-либо четкие сообщения о том, является ли это возможным обсуждением в рамках всей области? Если нет, то должно ли оно быть проведено в другом месте.
В конце концов ответ Брайана Гетца был:
Да, это уже изучается.
Тем не менее, позвольте мне установить некоторые реалистичные ожидания - функции языка / виртуальной машины имеют длительное время выполнения, даже кажущиеся тривиальными. Время для предложения новых идей о языковых особенностях для Java SE 8 в значительной степени прошло.
Так что, скорее всего, он никогда не был реализован, потому что он никогда не был частью области видимости. Это никогда не было предложено вовремя, чтобы быть рассмотренным.
В другом бурном обсуждении окончательных методов защиты на эту тему Брайан снова сказал:
И вы получили именно то, что вы хотели. Это именно то, что добавляет эта функция - множественное наследование поведения. Конечно, мы понимаем, что люди будут использовать их как черты. И мы упорно трудились, чтобы гарантировать, что модель наследования они предлагают простые и достаточно чистый, чтобы люди могли получить хорошие результаты делают это в широком разнообразии ситуаций. В то же время мы решили не выталкивать их за пределы того, что работает просто и чисто, и в некоторых случаях это приводит к реакциям "ты не зашел достаточно далеко". Но на самом деле, большая часть этой темы, кажется, ворчит, что стакан заполнен всего на 98%. Я возьму эти 98% и покончу с этим!
Так что это подтверждает мою теорию о том, что она просто не была частью объема или частью их дизайна. Что они сделали, так это предоставили достаточно функциональности для решения проблем эволюции API.
Будет трудно найти и определить ответ "THE", потому что резонаторы, упомянутые в комментариях @EJP: в мире примерно 2 (+/- 2) человека, которые вообще могут дать определенный ответ. И в сомнении ответ мог бы быть что-то вроде: "Поддержка окончательных методов по умолчанию не стоила усилий по реструктуризации внутренних механизмов разрешения вызовов". Конечно, это предположение, но оно, по крайней мере, подкреплено неуловимыми доказательствами, такими как это Заявление (одного из двух) в списке рассылки OpenJDK:
"Полагаю, если методы" окончательного по умолчанию "были разрешены, им, возможно, потребуется переписать с внутреннего invokespecial на видимый пользователем invokeinterface".
и тривиальные факты, подобные этому, метод просто не считается (действительно) окончательным методом, когда он default
метод, как в настоящее время реализовано в методе Method::is_final_method в OpenJDK.
Дальнейшую действительно "авторитетную" информацию действительно трудно найти, даже с чрезмерным поиском в сети и чтением журналов коммитов. Я думал, что это может быть связано с потенциальной неопределенностью во время разрешения вызовов метода интерфейса с помощью invokeinterface
вызовы инструкций и методов класса, соответствующие invokevirtual
инструкция: для invokevirtual
В инструкции может быть простой поиск в vtable, потому что метод должен быть либо унаследован от суперкласса, либо реализован классом напрямую. В отличие от этого, invokeinterface
Вызов должен проверить соответствующий сайт вызова, чтобы выяснить, к какому интерфейсу этот вызов относится (это более подробно описано на странице InterfaceCalls в HotSpot Wiki). Тем не мение, final
методы либо вообще не вставляются в vtable, либо заменяют существующие записи в vtable (см. klassVtable.cpp. Строка 333), и аналогично методы по умолчанию заменяют существующие записи в vtable (см. klassVtable.cpp, Line 202)., Таким образом, действительная причина (и, следовательно, ответ) должна быть скрыта глубже в (довольно сложных) механизмах разрешения вызовов методов, но, возможно, эти ссылки, тем не менее, будут рассматриваться как полезные, будь то только для тех, кому удастся получить фактический ответ От этого.
Я не думаю, что это необходимо, чтобы указать final
Что касается метода удобного интерфейса, я могу согласиться с тем, что он может быть полезным, но, по-видимому, затраты перевешивают преимущества.
В любом случае, вы должны написать правильный javadoc для метода по умолчанию, точно показывая, что этот метод разрешен и запрещен. Таким образом, классы, реализующие интерфейс, "не допускаются" для изменения реализации, хотя нет никаких гарантий.
Кто угодно мог написать Collection
который придерживается интерфейса, а затем делает что-то в методах, которые абсолютно нелогичны, нет никакого способа оградить себя от этого, кроме написания обширных модульных тестов.
Мы добавляем default
ключевое слово нашего метода внутри interface
когда мы знаем, что класс, расширяющий interface
может или не может override
наша реализация. Но что, если мы хотим добавить метод, который мы не хотим, чтобы реализующий класс переопределял? Что ж, нам было доступно два варианта:
- Добавить
default
final
метод. - Добавить
static
метод.
Теперь Java говорит, что если у нас есть class
реализация двух или более interfaces
так что у них есть default
метод с точно таким же именем метода и сигнатурой, то есть они дублируются, то нам нужно предоставить реализацию этого метода в нашем классе. Теперь в случаеdefault
final
методы, мы не можем предоставить реализацию и застряли. И поэтомуfinal
ключевое слово не используется в интерфейсах.