Почему "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?

Ответы

Ответ 1

Этот вопрос в какой-то степени связан с тем, что является причиной, по которой "синхронизация" не допускается в интерфейсных методах 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

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

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

Ответ 2

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

В этой дискуссии Talden, автор оригинального вопроса

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

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

В конце концов был:

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

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

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

В другом горячем обсуждении по теме :

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

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

Ответ 3

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

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

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

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

Ответ 4

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

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

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