Spec-Zone.ru › Java Language Specification 21

Глава 9. Интерфейсы

Содержание

9.1. Объявления интерфейсов
9.1.1. Модификаторы интерфейсов
9.1.1.1. abstract Интерфейсы
9.1.1.2. strictfp Интерфейсы
9.1.1.3. static Интерфейсы
9.1.1.4. sealed и non-sealed интерфейсы
9.1.2. Обобщенные интерфейсы и параметры типа
9.1.3. Супер- и под-интерфейсы
9.1.4. Разрешенные прямые подклассы и под-интерфейсы
9.1.5. Тело интерфейса и объявления членов
9.2. Члены интерфейса
9.3. Объявления полей (констант)
9.3.1. Инициализация полей в интерфейсах
9.4. Объявления методов
9.4.1. Наследование и переопределение
9.4.1.1. Переопределение (методами экземпляров)
9.4.1.2. Требования при переопределении
9.4.1.3. Наследование методов с эквивалентными подписи переопределения
9.4.2. Перегрузка
9.4.3. Тело метода интерфейса
9.5. Объявления вложенных классов и интерфейсов
9.6. Аннотационные интерфейсы
9.6.1. Элементы аннотационного интерфейса
9.6.2. Значения по умолчанию для элементов аннотационного интерфейса
9.6.3. Повторяющиеся аннотационные интерфейсы
9.6.4. Предопределенные аннотационные интерфейсы
9.6.4.1. @Target
9.6.4.2. @Retention
9.6.4.3. @Inherited
9.6.4.4. @Override
9.6.4.5. @SuppressWarnings
9.6.4.6. @Deprecated
9.6.4.7. @SafeVarargs
9.6.4.8. @Repeatable
9.6.4.9. @FunctionalInterface
9.7. Аннотации
9.7.1. Обычные аннотации
9.7.2. Аннотации-маркеры
9.7.3. Аннотации с единственным элементом
9.7.4. Местоположение аннотаций
9.7.5. Несколько аннотаций одного и того же интерфейса
9.8. Функциональные интерфейсы
9.9. Типы функций

Объявление интерфейса определяет новый интерфейс, который может быть реализован одним или несколькими классами. Программы могут использовать интерфейсы для предоставления общего супертипа для несвязанных классов и для того, чтобы избежать необходимости в общем abstract суперклассе для родственных классов.

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

Интерфейс верхнего уровня (§7.6) — это интерфейс, объявленный непосредственно в единице компиляции.

Вложенный интерфейс — это любой интерфейс, объявление которого находится внутри тела другого класса или интерфейса. Вложенный интерфейс может быть интерфейсом-членом (§8.5, §9.5) или локальным интерфейсом (§14.3).

Аннотационный интерфейс (§9.6) — это интерфейс, объявленный с отличной синтаксической конструкцией, предназначенный для реализации рефлексивных представлений аннотаций (§9.7).

В этой главе рассматриваются общие семантики всех интерфейсов. Подробности, специфичные для определённых видов интерфейсов, рассматриваются в разделах, посвящённых этим конструкциям.

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

Класс может быть объявлен прямо реализующим один или несколько интерфейсов (§8.1.5), что означает, что любой экземпляр класса реализует все abstract методы, указанные интерфейсом или интерфейсами. Класс обязательно реализует все интерфейсы, которые реализуют его прямые суперклассы и прямые супер-интерфейсы. Это (множественное) наследование интерфейсов позволяет объектам поддерживать (множественные) общие действия без общего суперкласса.

В отличие от класса, интерфейс нельзя объявить final. Однако, интерфейс может быть объявлен sealed (§9.1.1.4) для ограничения его подклассов и под-интерфейсов.

Переменная, тип которой объявлен как тип интерфейса, может иметь в качестве значения ссылку на любой экземпляр класса, который реализует указанный интерфейс. Недостаточно, чтобы класс случайно реализовывал все abstract методы интерфейса; класс или один из его суперклассов должен быть фактически объявлен как реализующий интерфейс, в противном случае класс не считается реализующим интерфейс.

END_OF_DOCUMENT_MARKER

9.1. Объявления интерфейсов

Объявление интерфейса определяет интерфейс.

Существуют два вида объявлений интерфейсов: обычные объявления интерфейсов и объявления интерфейсов-аннотаций (§9.6).

InterfaceDeclaration:
NormalInterfaceDeclaration
AnnotationInterfaceDeclaration
NormalInterfaceDeclaration:
{МодификаторИнтерфейса} interface ИдентификаторТипа [ПараметрыТипов] [ИнтерфейсыРасширения] [ИнтерфейсыРазрешения] ТелоИнтерфейса

ИдентификаторТипа в объявлении интерфейса указывает имя интерфейса.

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

Область действия и перекрытие объявления интерфейса указаны в §6.3 и §6.4.1.

9.1.1. Модификаторы интерфейсов

Объявление интерфейса может содержать модификаторы интерфейса.

InterfaceModifier:
(один из)
Аннотация public protected private
abstract static sealed non-sealed strictfp

Правила, касающиеся модификаторов аннотаций для объявления интерфейса, указаны в §9.7.4 и §9.7.5.

Модификатор доступа public (§6.6) относится только к интерфейсам верхнего уровня (§7.6) и вложенным интерфейсам (§8.5, §9.5), а не к локальным интерфейсам (§14.3).

Модификаторы доступа protected и private относятся только к вложенным интерфейсам.

Модификатор static относится только к вложенным и локальным интерфейсам.

Ошибка времени компиляции, если одно и то же ключевое слово появляется более одного раза как модификатор для объявления интерфейса, или если объявление интерфейса имеет более одного из модификаторов доступа public, protected и private.

Ошибка времени компиляции, если объявление интерфейса имеет более одного из модификаторов sealed и non-sealed.

Если в объявлении интерфейса присутствуют два или более (различных) модификаторов интерфейса, то обычно, хотя и не обязательно, они располагаются в порядке, согласованном с представленным выше в выражении для МодификатораИнтерфейса.

9.1.1.1. abstract Интерфейсы

Каждый интерфейс неявным образом abstract.

Этот модификатор устарел и не должен использоваться в новом коде.

9.1.1.2. strictfp Интерфейсы

Модификатор strictfp в объявлении интерфейса устарел и не должен использоваться в новом коде. Его присутствие или отсутствие не влияет на время компиляции или выполнения.

9.1.1.3. static Интерфейсы

Вложенный интерфейс неявным образом static. То есть каждый вложенный и локальный интерфейс static. Допускается для объявления вложенного интерфейса избыточно указывать модификатор static (§9.5), но недопустимо для объявления локального интерфейса (§14.3).

Поскольку вложенный интерфейс static, у него нет непосредственного окружающего экземпляра (§8.1.3). Ссылки из вложенного интерфейса на параметры типа, переменные экземпляра, локальные переменные, формальные параметры, параметры исключений или методы экземпляра в лексически окружающих класс, интерфейс или метод объявления запрещены (§6.5.5.1, §6.5.6.1, §15.12.3).

9.1.1.4. sealed и non-sealed интерфейсы

Интерфейс может быть объявлен sealed, если все его непосредственные подклассы и непосредственные подинтерфейсы известны при объявлении интерфейса (§9.1.4), и другие непосредственные подклассы или непосредственные подинтерфейсы не требуются.

Полезно вспомнить, что класс считается непосредственным подклассом своих непосредственных суперинтерфейсов (§8.1.5).

Интерфейс является свободно расширяемым, если ни один из его непосредственных суперинтерфейсов не является sealed (§9.1.3), и он сам не является sealed.

Интерфейс, имеющий sealed непосредственный суперинтерфейс, является свободно расширяемым, только если он объявлен non-sealed.

Ошибка времени компиляции, если интерфейс имеет sealed непосредственный суперинтерфейс и не объявлен sealed или non-sealed.

Ошибка времени компиляции, если интерфейс объявлен non-sealed, но не имеет sealed непосредственного суперинтерфейса.

9.1.2. Обобщенные интерфейсы и параметры типов

Интерфейс является обобщенным, если объявление интерфейса содержит одну или несколько переменных типа (§4.4).

Эти переменные типа известны как параметры типа интерфейса. Раздел параметров типа следует за именем интерфейса и ограничен угловыми скобками.

Следующие правила из §8.1.2 и §4.4 представлены здесь для удобства:

TypeParameters:
< Список параметров типа >
TypeParameterList:
Параметр типа {, Параметр типа}
TypeParameter:
{Модификатор параметра типа} Идентификатор типа [Граница типа]
TypeParameterModifier:
Аннотация
TypeBound:
extends Переменная типа
extends Тип класса или интерфейса {Дополнительная граница}
AdditionalBound:
& Тип интерфейса

Правила, касающиеся модификаторов аннотаций для объявления параметра типа, указаны в §9.7.4 и §9.7.5.

В разделе параметров типа интерфейса переменная типа T непосредственно зависит от переменной типа S, если S является границей T, в то время как T зависит от S, если либо T непосредственно зависит от S, либо T непосредственно зависит от переменной типа U, которая зависит от S (используя это определение рекурсивно). Если переменная типа в разделе параметров типа интерфейса зависит от самой себя, это является ошибкой компиляции.

Область действия и перекрытие параметра типа интерфейса указаны в §6.3 и §6.4.1.

Ссылки на параметр типа интерфейса из статического контекста или вложенного класса или интерфейса ограничены, как указано в §6.5.5.1.

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

9.1.3. Суперинтерфейсы и подинтерфейсы

Если указан раздел extends, то объявляемый интерфейс расширяет каждый из указанных типов интерфейса и, следовательно, наследует классы-члены, интерфейсы-члены, методы экземпляров и static поля каждого из этих типов интерфейса.

Указанные типы интерфейсов являются непосредственными типами суперинтерфейсов объявляемого интерфейса.

Любой класс, который implements объявленный интерфейс, также считается реализующим все интерфейсы, которые этот интерфейс extends.

InterfaceExtends:
extends Список типов интерфейсов

Следующее правило из §8.1.5 приводится здесь для удобства:

InterfaceTypeList:
Тип интерфейса {, Тип интерфейса}

Каждый Тип интерфейса в разделе extends объявления интерфейса должен указывать доступный интерфейс (§6.6), в противном случае произойдет ошибка компиляции.

Если любой Тип интерфейса указывает интерфейс, который является sealed (§9.1.1.4), и объявляемый интерфейс не является разрешенным непосредственным подинтерфейсом указанного интерфейса (§9.1.4), это является ошибкой компиляции.

Если у Тип интерфейса есть аргументы типа, он должен обозначать корректный параметризованный тип (§4.5), и ни один из аргументов типа не должен быть аргументами типа с подстановочными знаками; в противном случае произойдет ошибка компиляции.

Один интерфейс является непосредственным суперинтерфейсом другого интерфейса, если первый интерфейс указан одним из непосредственных типов суперинтерфейсов второго интерфейса.

Отношение суперинтерфейс является транзитивным замыканием отношения непосредственного суперинтерфейса. Интерфейс I является суперинтерфейсом интерфейса K, если выполняется одно из следующих условий:

  • I является непосредственным суперинтерфейсом K.

  • Где J является непосредственным суперинтерфейсом K, I является суперинтерфейсом J, применяя это определение рекурсивно.

Интерфейс считается непосредственным подинтерфейсом своего непосредственного суперинтерфейса и подинтерфейсом каждого из своих суперинтерфейсов.

Хотя каждый класс является расширением класса Object, не существует единого интерфейса, которым являются расширения всех интерфейсов.

Интерфейс I непосредственно зависит от класса или интерфейса A, если A указан в разделе extends интерфейса I либо как суперинтерфейс, либо как квалификатор в полном квалифицированном имени суперинтерфейса.

Интерфейс I зависит от класса или интерфейса A, если выполняется одно из следующих условий:

  • I непосредственно зависит от A.

  • I непосредственно зависит от класса C, который зависит от A (§8.1.5).

  • I непосредственно зависит от интерфейса J, который зависит от A, применяя это определение рекурсивно.

Если интерфейс зависит от самого себя, это является ошибкой компиляции.

Если при загрузке интерфейсов обнаруживаются циклически объявленные интерфейсы, то выбрасывается ClassCircularityError (§12.2.1).

9.1.4. Разрешённые прямые подклассы и подинтерфейсы

Необязательная permits фраза в объявлении обычного интерфейса указывает все классы и интерфейсы, предназначенные в качестве прямых подклассов и прямых подинтерфейсов объявляемого интерфейса (§9.1.1.4).

InterfacePermits:
permits ИмяТипа {, ИмяТипа}

Если в объявлении интерфейса есть фраза permits, но отсутствует модификатор sealed, то это ошибка компиляции.

Каждый ИмяТипа должен указывать на доступный класс или интерфейс (§6.6), иначе произойдёт ошибка компиляции.

Если один и тот же класс или интерфейс указан более одного раза во фразе permits, то это ошибка компиляции. Это верно даже если класс или интерфейс назван по-разному.

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

package p;

sealed interface I permits C, D, p.C {}  // error

non-sealed class C implements I {}  
non-sealed class D implements I {}

Если интерфейс sealed I ассоциирован с именованным модулем (§7.3), то каждый класс или интерфейс, указанный во фразе permits объявления I, должен быть ассоциирован с тем же модулем, что и I, иначе произойдёт ошибка компиляции.

Если интерфейс sealed I ассоциирован с безымянным модулем (§7.7.5), то каждый класс или интерфейс, указанный во фразе permits объявления I, должен принадлежать к тому же пакету, что и I, иначе произойдёт ошибка компиляции.

Интерфейс sealed и его прямые подклассы и прямые подинтерфейсы должны ссылаться друг на друга циклическим способом, соответственно, во фразах permits, implements и extends. Поэтому в модульной кодовой базе они должны быть размещены в одном модуле, так как классы и интерфейсы в разных модулях не могут ссылаться друг на друга циклическим образом. Совместное размещение желательно в любом случае, потому что иерархия интерфейса sealed всегда должна быть объявлена в одном домене поддержки, за которым отвечает один и тот же разработчик или группа разработчиков. Именованный модуль обычно представляет собой домен поддержки в модульной кодовой базе.

Если объявление интерфейса sealed I содержит фразу permits, то разрешённые прямые подклассы и подинтерфейсы интерфейса I представляют собой классы и интерфейсы, указанные во фразе permits.

Каждый разрешённый прямой подкласс и подинтерфейс, указанный во фразе permits, должен быть прямым подклассом интерфейса I (§8.1.5) или прямым подинтерфейсом I (§9.1.3), иначе произойдёт ошибка компиляции.

Если объявление интерфейса sealed I не содержит фразу permits, то разрешённые прямые подклассы и подинтерфейсы интерфейса I — это те классы и интерфейсы, которые объявлены в той же единице компиляции, что и I (§7.3), имеют каноническое имя (§6.7) и для которых прямые суперинтерфейсы включают интерфейс I.

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

Если объявление интерфейса sealed I не содержит фразу permits и у I нет разрешённых прямых подклассов или подинтерфейсов, то это ошибка компиляции.

9.1.5. Тело интерфейса и объявления членов

Тело интерфейса может содержать объявления членов интерфейса, то есть поля (§9.3), методы (§9.4), классы и интерфейсы (§9.5).

InterfaceBody:
{ {ОбъявлениеЧленаИнтерфейса} }
InterfaceMemberDeclaration:
ОбъявлениеКонстанты
ОбъявлениеМетодаИнтерфейса
ОбъявлениеКласса
ОбъявлениеИнтерфейса
;

Область видимости объявления члена m, объявленного в или унаследованного интерфейсом I, указана в §6.3.

9.2. Члены интерфейса

Членами интерфейса являются:

  • Члены, объявленные в теле объявления интерфейса (§9.1.5).

  • Члены, унаследованные от любых прямых суперинтерфейсов (§9.1.3).

  • Если у интерфейса нет прямых суперинтерфейсов, то интерфейс неявно объявляет метод public abstract для каждого public экземпляра метода m с сигнатурой s, возвращаемым типом r и throws фразрой t, объявленного в Object (§4.3.2), за исключением случаев, когда интерфейс явно объявляет метод с той же сигнатурой, тем же возвращаемым типом и совместимой throws фразой.

    Ошибка компиляции, если интерфейс явно объявляет такой метод m в случае, когда m объявлен как final в Object.

    Ошибка компиляции, если интерфейс явно объявляет метод с сигнатурой, которая эквивалентна переопределению (§8.4.2) для public метода Object, но с другим возвращаемым типом или несовместимой throws фразой, или не является abstract.

Интерфейс наследует от интерфейсов, которые он расширяет, все члены этих интерфейсов, за исключением (i) полей, классов и интерфейсов, которые он скрывает, (ii) abstract методов и методов по умолчанию, которые он переопределяет (§9.4.1), (iii) private методов и (iv) static методов.

Поля, методы, вложенные классы и вложенные интерфейсы интерфейса могут иметь одинаковые имена, поскольку они используются в разных контекстах и различаются различными процедурами поиска (§6.5). Однако это не рекомендуется с точки зрения стиля.

END_OF_DOCUMENT_MARKER

9.3. Объявления полей (констант)

ConstantDeclaration:
{МодификаторКонстанты} ТипБезАннотаций СписокДескрипторовПеременных ;
ConstantModifier:
(одно из)
Аннотация public
static final

См. §8.3 для ТипБезАннотаций. Следующие правила из §4.3 и §8.3 показаны здесь для удобства:

VariableDeclaratorList:
ДескрипторПеременной {, ДескрипторПеременной}
VariableDeclarator:
ИдентификаторДескриптораПеременной [= ИнициализаторПеременной]
VariableDeclaratorId:
Идентификатор [Размеры]
Dims:
{Аннотация} [ ] {{Аннотация} [ ]}
VariableInitializer:
Выражение
ИнициализаторМассива

Правила, касающиеся модификаторов аннотаций для объявления поля интерфейса, указаны в §9.7.4 и §9.7.5.

Каждое объявление поля в теле объявления интерфейса подразумевает public, static и final. Разрешается избыточно указывать любой или все эти модификаторы для таких полей.

Если один и тот же ключевое слово появляется более одного раза как модификатор для объявления поля, возникает ошибка компиляции.

Если в объявлении поля появляются два или более (различных) модификатора поля, то принято (но не обязательно), что они появляются в порядке, согласованном с представленным выше правилом для МодификаторКонстанты.

Объявленный тип поля обозначается ТипБезАннотаций, если в ТипБезАннотаций и ИдентификаторДескриптораПеременной не появляются скобки, и определяется в §10.2 в противном случае.

Область действия и перекрытие объявления поля интерфейса указаны в §6.3 и §6.4.1.

Поскольку поле интерфейса является static, его объявление вводит статический контекст (§8.1.3), который ограничивает использование конструкций, ссылающихся на текущий объект. Отметим, что ключевые слова this и super запрещены в статическом контексте (§15.8.3, §15.11.2), как и неквалифицированные ссылки на переменные экземпляра, методы экземпляра и параметры типа лексически окружающих объявлений (§6.5.5.1, §6.5.6.1, §15.12.3).

Если тело объявления интерфейса объявляет два поля с одним именем, возникает ошибка компиляции.

Если интерфейс объявляет поле с определенным именем, то это объявление поля скрывает все доступные объявления полей с тем же именем в суперинтерфейсах интерфейса.

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

Существует несколько путей наследования одного и того же объявления поля из интерфейса. В такой ситуации поле считается унаследованным только один раз, и на него можно ссылаться по его простому имени без неоднозначности.

Пример 9.3-1. Неоднозначные унаследованные поля

Если два поля с одинаковым именем унаследованы интерфейсом, например, из-за того, что два его непосредственных суперинтерфейса объявляют поля с этим именем, то возникает один неоднозначный член. Любое использование этого неоднозначного члена приведет к ошибке компиляции. В программе:

interface BaseColors {
    int RED = 1, GREEN = 2, BLUE = 4;
}
interface RainbowColors extends BaseColors {
    int YELLOW = 3, ORANGE = 5, INDIGO = 6, VIOLET = 7;
}
interface PrintColors extends BaseColors {
    int YELLOW = 8, CYAN = 16, MAGENTA = 32;
}
interface LotsOfColors extends RainbowColors, PrintColors {
    int FUCHSIA = 17, VERMILION = 43, CHARTREUSE = RED+90;
}

интерфейс LotsOfColors наследует два поля с именем YELLOW. Это нормально, пока интерфейс не содержит ссылки по простому имени на поле YELLOW. (Такая ссылка может появиться в инициализаторе переменной для поля.)

Даже если интерфейс PrintColors присвоит значение 3 полю YELLOW, а не значение 8, ссылка на поле YELLOW внутри интерфейса LotsOfColors все равно будет считаться неоднозначной.


Пример 9.3-2. Поля, унаследованные несколько раз

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

В предыдущем примере поля RED, GREEN и BLUE унаследованы интерфейсом LotsOfColors более чем одним способом, через интерфейс RainbowColors и также через интерфейс PrintColors, но ссылка на поле RED в интерфейсе LotsOfColors не считается неоднозначной, потому что участвует только одно фактическое объявление поля RED.


9.3.1. Инициализация полей в интерфейсах

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

Инициализатор не обязательно должен быть константным выражением (§15.29).

Если инициализатор поля интерфейса использует простое имя того же поля или другого поля, объявление которого расположено справа от инициализатора (§3.5) в том же интерфейсе, возникает ошибка компиляции.

Инициализатор поля интерфейса не может ссылаться на текущий объект с помощью ключевого слова this или ключевого слова super, как указано в §15.8.3, §15.11.2 и §15.12.3.

Во время выполнения инициализатор вычисляется, и присваивание поля выполняется ровно один раз при инициализации интерфейса (§12.4.2).

Обратите внимание, что поля интерфейса, являющиеся константами (§4.12.4), инициализируются до других полей интерфейса. Это также относится к static полям, которые являются константами в классах (§8.3.2). Такие поля никогда не будут наблюдаться с их значениями по умолчанию (§4.12.5), даже хитрыми программами.

Пример 9.3.1-1. Вперед ссылка на поле

interface Test {
    float f = j;
    int   j = 1;
    int   k = k + 1;
}

Эта программа вызывает две ошибки компиляции, потому что j ссылается на f до объявления j, а инициализация k ссылается на само k.


9.4. Объявления методов

InterfaceMethodDeclaration:
{МодификаторМетодаИнтерфейса} ЗаголовокМетода ТелоМетода
InterfaceMethodModifier:
(один из)
Аннотация public private
abstract default static strictfp

Следующие правила из §8.4, §8.4.5 и §8.4.7 приведены здесь для удобства:

MethodHeader:
Результат ДеклараторМетода [Исключения]
ПараметрыТипов {Аннотация} Результат ДеклараторМетода [Исключения]
Результат:
ТипБезАннотаций
void
ДеклараторМетода:
Идентификатор ( [ПараметрПолучателя ,] [СписокФормальныхПараметров] ) [Размеры]
ТелоМетода:
Блок
;

Правила, касающиеся модификаторов аннотаций для объявления метода интерфейса, указаны в §9.7.4 и §9.7.5.

Метод в теле объявления интерфейса может быть объявлен public или private (§6.6). Если модификатор доступа не указан, метод неявно public. Разрешается, но не рекомендуется по стилю, избыточно указывать модификатор public для объявления метода в объявлении интерфейса.

Метод по умолчанию — это метод-инстанс, объявленный в интерфейсе с модификатором default. Его тело всегда представлено блоком, который предоставляет реализацию по умолчанию для любого класса, реализующего интерфейс, без переопределения метода. Методы по умолчанию отличаются от конкретных методов (§8.4.3.1), которые объявляются в классах, и от private методов интерфейса, которые ни наследуются, ни переопределяются.

Интерфейс может объявлять static методы, которые вызываются без ссылки на конкретный объект. static методы интерфейса отличаются от методов по умолчанию, abstract методов интерфейса и не-static private методов интерфейса, все из которых являются методами-инстансами.

Объявление static метода интерфейса вводит статический контекст (§8.1.3), который ограничивает использование конструкций, ссылающихся на текущий объект. Отметим, что ключевые слова this и super запрещены в статическом контексте (§15.8.3, §15.11.2), как и неквалифицированные ссылки на переменные экземпляра, методы экземпляра и параметры типа лексически окружающих объявлений (§6.5.5.1, §6.5.6.1, §15.12.3).

Ссылки на метод экземпляра из статического контекста или вложенного класса или интерфейса ограничены (§15.12.3).

Модификатор strictfp для объявления метода интерфейса устарел и не должен использоваться в новом коде. Его присутствие или отсутствие не влияет во время выполнения.

Метод интерфейса без модификатора private, default или static неявно abstract. Его тело представлено точкой с запятой, а не блоком. Разрешается, но не рекомендуется по стилю, избыточно указывать модификатор abstract для такого объявления метода.

Обратите внимание, что метод интерфейса не может быть объявлен с protected или пакетом доступа, или с модификаторами final, synchronized или native.

Если одно и то же ключевое слово появляется более одного раза в качестве модификатора для объявления метода интерфейса, или если объявление метода интерфейса имеет более одного модификатора доступа public и private (§6.6), то это ошибка компиляции.

Если объявление метода интерфейса имеет более одного из ключевых слов abstract, default или static, то это ошибка компиляции.

Если объявление метода интерфейса, содержащее ключевое слово private, также содержит ключевое слово abstract или default, то это ошибка компиляции. Разрешено объявление метода интерфейса, содержащее как private, так и static.

Если объявление метода интерфейса, содержащее ключевое слово abstract, также содержит ключевое слово strictfp, то это ошибка компиляции.

Ошибка компиляции, если тело объявления интерфейса объявляет явно или неявно два метода с эквивалентными по переопределению сигнатурами (§8.4.2). Однако интерфейс может унаследовать несколько abstract методов с такими сигнатурами (§9.4.1).

Метод, объявленный в интерфейсе, может быть обобщенным. Правила для параметров типа обобщенного метода в интерфейсе такие же, как для обобщенного метода в классе (§8.4.4).

9.4.1. Наследование и переопределение

Интерфейс I унаследует от своих непосредственных суперинтерфейсов все abstract и методы по умолчанию m, для которых выполняются все следующие условия:

  • m является членом непосредственного суперинтерфейса типа I, J.

  • Ни один метод, объявленный в I, не имеет сигнатуры, являющейся подсигнатурой (§8.4.2) сигнатуры m как члена J.

  • Не существует метода m', который является членом непосредственного суперинтерфейса I, J' (m отличающегося от m', J отличается от J'), такого, что m' переопределяет объявление метода m из интерфейса J' (§9.4.1.1).

Обратите внимание, что методы переопределяются на основе сигнатуры. Если, например, интерфейс объявляет два public метода с одинаковым именем (§9.4.2), и подинтерфейс переопределяет один из них, подинтерфейс все равно наследует другой метод.

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

interface Top {
    default String name() { return "unnamed"; }
}
interface Left extends Top {
    default String name() { return getClass().getName(); }
}
interface Right extends Top {}

interface Bottom extends Left, Right {}

Right наследует name() от Top, но Bottom наследует name() от Left, а не от Right. Это происходит потому, что name() из Left переопределяет объявление name() в Top.

Интерфейс не наследует private или static методы от своих суперинтерфейсов.

Если интерфейс I объявляет private или static метод m, и сигнатура m является подсигнатурой public метода экземпляра m' в супертипе I, и m' в противном случае был бы доступен коду в I, то происходит ошибка во время компиляции.

По существу, метод static в интерфейсе не может скрыть метод экземпляра в суперинтерфейсе. Это аналогично правилу в §8.4.8.2, где метод static в классе не может скрыть метод экземпляра в суперклассе или суперинтерфейсе. Обратите внимание, что правило в §8.4.8.2 говорит о классе, который "объявляет или наследует static метод", в то время как правило выше говорит только об интерфейсе, который "объявляет static метод", поскольку интерфейс не может унаследовать static метод. Также обратите внимание, что правило в §8.4.8.2 позволяет скрывать методы экземпляров и static методы в суперклассах/суперинтерфейсах, в то время как правило выше рассматривает только public методы экземпляров в суперинтерфейсах.

В том же духе, private метод в интерфейсе не может переопределить метод экземпляра - будь то public или private - в суперинтерфейсе. Это аналогично правилам в §8.4.8.1 и §8.4.8.3, где метод private в классе не может переопределить никакой метод экземпляра в суперклассе или суперинтерфейсе, так как §8.4.8.1 требует, чтобы переопределяемый метод был не-private, а §8.4.8.3 требует, чтобы переопределяющий метод предоставлял не менее доступа, чем переопределяемый метод. В заключение, только public методы в интерфейсах могут быть переопределены, и только public методами в подинтерфейсах или в реализующих классах.

9.4.1.1. Переопределение (методами экземпляров)

Метод экземпляра mI, объявленный в интерфейсе I или унаследованный от него, переопределяет из I другой метод экземпляра mJ, объявленный в интерфейсе J, если выполняются все следующие условия:

  • I является подинтерфейсом J.

  • I не наследует mJ.

  • Сигнатура mI является подсигнатурой (§8.4.2) сигнатуры mJ как члена супертипа I, который называет J.

  • mJ является public.

Наличие или отсутствие модификатора strictfp совершенно не влияет на правила переопределения методов. Например, разрешено, чтобы метод, который не является strictfp, переопределял strictfp метод, и разрешено, чтобы strictfp метод переопределял метод, который не является strictfp.

К методу по умолчанию, который был переопределен, можно получить доступ, используя выражение вызова метода (§15.12), содержащее ключевое слово super, квалифицированное именем суперинтерфейса.

9.4.1.2. Требования к переопределению

Отношения между типом возвращаемого значения метода интерфейса и типами возвращаемых значений любых переопределяемых методов интерфейса указаны в §8.4.8.3.

Отношения между throws частью метода интерфейса и throws частями любых переопределяемых методов интерфейса указаны в §8.4.8.3.

Отношения между сигнатурой метода интерфейса и сигнатурами любых переопределяемых методов интерфейса указаны в §8.4.8.3.

Отношения между доступом к методу интерфейса и доступом к любым переопределяемым методам интерфейса указаны в §8.4.8.3.

Если метод по умолчанию эквивалентен переопределению (§8.4.2) с методом, не являющимся private методом класса Object, это ошибка времени компиляции, так как любой класс, реализующий интерфейс, унаследует собственную реализацию метода.

Запрет на объявление одного из методов Object в качестве метода по умолчанию может показаться неожиданным. В конце концов, есть такие случаи, как java.util.List, в которых поведение toString и equals точно определены. Однако мотивация становится яснее, когда понимаются некоторые более общие решения по проектированию:

  • Во-первых, методы, унаследованные от суперкласса, могут переопределять методы, унаследованные от суперинтерфейсов (§8.4.8.1). Таким образом, каждый реализующий класс автоматически переопределит toString метод по умолчанию интерфейса. Это давнее поведение языка программирования Java. Это не то, что мы хотим изменить с помощью методов по умолчанию, поскольку это противоречит цели позволяющей интерфейсам эволюционировать незаметно, предоставляя поведение по умолчанию только тогда, когда класс уже не имеет его через иерархию классов.

  • Во-вторых, интерфейсы не наследуют от Object, а скорее неявно объявляют многие из тех же методов, что и Object (§9.2). Таким образом, нет общего предка для toString, объявленного в Object, и toString, объявленного в интерфейсе. В лучшем случае, если оба были кандидатами на наследование классом, они вступили бы в конфликт. Обход этой проблемы потребовал бы неуклюжего смешения древовидных структур наследования классов и интерфейсов.

  • Во-третьих, случаи использования объявления методов Object в интерфейсах обычно предполагают линейную иерархию интерфейсов; функция не обобщается хорошо на сценарии множественного наследования.

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

Однако интерфейс свободен определять другой метод, который предоставляет поведение, полезное для классов, переопределяющих методы Object. Например, интерфейс java.util.List может объявить метод elementString, который генерирует строку, описанную контрактом метода toString; реализаторы метода toString в классах могли бы делегировать этому методу.

9.4.1.3. Наследование методов с эквивалентными сигнатурами переопределения

Интерфейс может унаследовать несколько методов с эквивалентными сигнатурами переопределения (§8.4.2).

Если интерфейс I наследует метод по умолчанию, чья сигнатура эквивалентна переопределению другому методу, унаследованному интерфейсом I, возникает ошибка времени компиляции. (Это происходит, независимо от того, является ли другой метод abstract или default.)

В противном случае все унаследованные методы являются abstract, и интерфейс считается унаследовавшим все методы.

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

Возможно несколько путей, по которым одна и та же декларация метода унаследована от интерфейса. Этот факт не создаёт трудностей и никогда сам по себе не приводит к ошибке времени компиляции.

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

Аналогично, когда abstract метод и default метод с совпадающими сигнатурами наследуются подинтерфейсом, мы выдаём ошибку. В этом случае можно было бы отдать приоритет одному из них — возможно, мы бы предположили, что метод по умолчанию предоставляет разумную реализацию для abstract метода. Но это рискованно, поскольку, кроме случайного совпадения имени и сигнатуры, у нас нет оснований полагать, что поведение метода по умолчанию согласуется с контрактом abstract метода — метод по умолчанию мог вообще не существовать во время первоначального разработки подинтерфейса. В этой ситуации безопаснее попросить пользователя явно указать, что реализация по умолчанию является подходящей (путем объявления переопределения).

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

9.4.2. Перегрузка

Если два метода интерфейса (будь то объявленные в одном интерфейсе, или унаследованные одним интерфейсом, или один объявленный, а другой унаследованный) имеют одинаковое имя, но разные сигнатуры, которые не эквивалентны переопределению (§8.4.2), то имя метода называется перегруженным.

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

Пример 9.4.2-1. Перегрузка объявления метода abstract

interface PointInterface {
    void move(int dx, int dy);
}
interface RealPointInterface extends PointInterface {
    void move(float dx, float dy);
    void move(double dx, double dy);
}

Здесь метод с именем move перегружен в интерфейсе RealPointInterface с тремя разными сигнатурами, две из которых объявлены, а одна унаследована. Любой не-abstract класс, реализующий интерфейс RealPointInterface, должен предоставить реализации всех трёх сигнатур метода.


9.4.3. Тело метода интерфейса

Метод по умолчанию имеет тело блока. Этот блок кода обеспечивает реализацию метода в случае, если класс реализует интерфейс, но не предоставляет собственную реализацию метода.

Метод интерфейса private или static также имеет тело блока, которое обеспечивает реализацию метода.

Ошибка времени компиляции, если объявление метода интерфейса abstract (явное или неявное) и имеет блок для своего тела.

Ошибка времени компиляции, если объявление метода интерфейса default, private или static, и имеет точку с запятой для своего тела.

Правила для return операторов в теле метода указаны в §14.17.

Если метод объявлен с типом возврата (§8.4.5), возникает ошибка времени компиляции, если тело метода может завершиться нормально (§14.1).

9.5. Объявления вложенных классов и интерфейсов

Тело интерфейса (§9.1.5) может содержать объявления вложенных классов и вложенных интерфейсов (§8.5).

Каждое объявление вложенного класса или интерфейса в теле объявления интерфейса неявно public и static (§9.1.1.3). Разрешается избыточно указывать один или оба этих модификатора.

Если объявление вложенного класса или интерфейса в интерфейсе имеет модификатор protected или private, это ошибка компиляции.

Правила модификаторов объявления вложенного класса в теле объявления интерфейса указаны в §8.1.1.

Правила модификаторов объявления вложенного интерфейса в теле объявления интерфейса указаны в §9.1.1.

Если интерфейс объявляет вложенный класс или интерфейс с определенным именем, то объявление этого вложенного класса или интерфейса считается скрывающим все доступные объявления вложенных классов и интерфейсов с тем же именем в суперинтерфейсах интерфейса.

Интерфейс наследует от своих непосредственных суперинтерфейсов все вложенные классы и интерфейсы непосредственных суперинтерфейсов, которые не скрыты объявлением в интерфейсе.

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

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

9.6. Интерфейсы аннотаций

Объявление интерфейса аннотации определяет интерфейс аннотации, специализированный вид интерфейса. Чтобы отличить объявление интерфейса аннотации от обычного объявления интерфейса, ключевое слово interface предшествует знаку "@" (@).

AnnotationInterfaceDeclaration:
{InterfaceModifier} @ interface TypeIdentifier AnnotationInterfaceBody

Обратите внимание, что знак "@" (@) и ключевое слово interface — это отдельные лексемы. Их можно разделить пробелами, но это не рекомендуется с точки зрения стиля.

Если в этом разделе и его подразделах не указано иное, все правила, применимые к обычным объявлениям интерфейсов (§9.1), применяются к объявлениям интерфейсов аннотаций.

Например, для объявления интерфейсов аннотаций действуют те же правила области видимости, что и для обычных объявления интерфейсов.

Если объявление интерфейса аннотации имеет модификатор sealed или non-sealed (§9.1.1.4), это ошибка компиляции.

Объявление интерфейса аннотации может определять верхнеуровневый интерфейс или вложенный интерфейс, но не локальный интерфейс (§14.3).

Синтаксически объявление интерфейса аннотации не может находиться внутри блока в силу производства LocalClassOrInterfaceDeclaration в §14.3.

Если объявление интерфейса аннотации появляется непосредственно или косвенно в теле локального класса, локального интерфейса или анонимного класса, это ошибка компиляции (§14.3, §15.9.5).

Это правило, вместе со синтаксическим ограничением на объявления интерфейсов аннотаций, упомянутым выше, гарантирует, что интерфейс аннотации всегда имеет каноническое имя (§6.7). Наличие такого имени важно, потому что цель интерфейса аннотации — использоваться аннотациями в других модулях компиляции. Поскольку локальный класс или интерфейс не имеет канонического имени, объявленный где-либо в его синтаксическом теле (если это было бы разрешено) интерфейс аннотации тоже не имел бы канонического имени.

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

class C {
    @interface A1 {}  /* Legal: an annotation interface can be a
                         member interface */

    void m() {
        @interface A2 {}  /* Illegal: an annotation interface cannot
                             be a local interface */

        class D {
            @interface A3 {}  /* Illegal: an annotation interface
                                 cannot be specified anywhere within
                                 the body of local class D */

            class E {
                @interface A4 {}
                  /* Illegal: an annotation interface cannot be
                     specified anywhere within the body of local class
                     D, even as a member of a class E nested in D */
            }
        }
    }
}

Интерфейс аннотации никогда не является обобщенным (§9.1.2).

В отличие от обычного объявления интерфейса, объявление интерфейса аннотации не может объявлять переменные типов, в силу производства AnnotationTypeDeclaration.

Прямой тип суперинтерфейса интерфейса аннотации всегда java.lang.annotation.Annotation (§9.1.3).

В отличие от обычного объявления интерфейса, объявление интерфейса аннотации не может выбрать прямой тип суперинтерфейса через extends, в силу производства AnnotationTypeDeclaration.

Следствие из того, что объявление интерфейса аннотации не указывает явно тип суперинтерфейса через extends, состоит в том, что подинтерфейс интерфейса аннотации никогда сам по себе не является интерфейсом аннотации, так как в объявлении подинтерфейса обязательно используется extends. Аналогично, java.lang.annotation.Annotation сам по себе не является интерфейсом аннотации.

Интерфейс аннотации наследует несколько методов от java.lang.annotation.Annotation, включая неявно объявленные методы, соответствующие методам экземпляров Object (§9.2), но эти методы не определяют элементы интерфейса аннотации (§9.6.1).

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

END_OF_DOCUMENT_MARKER

9.6.1. Элементы интерфейса аннотаций

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

AnnotationInterfaceBody:
{ {AnnotationInterfaceMemberDeclaration} }
AnnotationInterfaceMemberDeclaration:
AnnotationInterfaceElementDeclaration
ConstantDeclaration
ClassDeclaration
InterfaceDeclaration
;
AnnotationInterfaceElementDeclaration:
{AnnotationInterfaceElementModifier} UnannType Identifier ( ) [Dims] [DefaultValue] ;
AnnotationInterfaceElementModifier:
(one of)
Annotation public
abstract

Следующее производство из §4.3 показано здесь для удобства:

Dims:
{Annotation} [ ] {{Annotation} [ ]}

В силу приведенной выше грамматики, объявление метода в объявлении интерфейса аннотации не может иметь формальные параметры, параметры типа или throws-запись; и не может быть private, default или static. Таким образом, у интерфейса аннотаций не может быть такого же разнообразия методов, как у обычного интерфейса. Обратите внимание, что интерфейс аннотаций все еще может унаследовать метод по умолчанию от своего неявного супер-интерфейса, java.lang.annotation.Annotation, хотя такого метода по умолчанию не существует по состоянию на Java SE 21.

По соглашению, единственными модификаторами, которые должны быть присутствовать в объявлении элемента интерфейса аннотации, являются аннотации.

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

  • Примитивный тип

  • String

  • Class или вызов Class (§4.5)

  • Тип перечисления

  • Тип интерфейса аннотации

  • Тип массива, у которого тип компонента является одним из указанных выше типов (§10.1).

Это правило исключает элементы с вложенными типами массивов, такие как:

@interface Verboten {
    String[][] value();
}

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

Ошибка компиляции возникает, если любой метод, объявленный в интерфейсе аннотации, имеет сигнатуру, эквивалентную с точки зрения переопределения (§8.4.2) сигнатуре любого public или protected метода, объявленного в классе Object или в интерфейсе java.lang.annotation.Annotation.

Ошибка компиляции возникает, если объявление интерфейса аннотаций T содержит элемент типа T, прямо или косвенно.

Например, это недопустимо:

@interface SelfRef { SelfRef value(); }

и это тоже:

@interface Ping { Pong value(); }
@interface Pong { Ping value(); }

Интерфейс аннотации без элементов называется маркерным интерфейсом аннотации.

Интерфейс аннотации с одним элементом называется интерфейсом аннотации с одним элементом.

По соглашению, имя единственного элемента в интерфейсе аннотации с одним элементом — value. Языковая поддержка этого соглашения предоставляется аннотациями с одним элементом (§9.7.3).

Пример 9.6.1-1. Объявление интерфейса аннотаций

В следующем объявлении интерфейса аннотаций определен интерфейс аннотаций с несколькими элементами:

/**
 * Describes the "request-for-enhancement" (RFE)
 * that led to the presence of the annotated API element.
 */
@interface RequestForEnhancement {
    int    id();        // Unique ID number associated with RFE
    String synopsis();  // Synopsis of RFE
    String engineer();  // Name of engineer who implemented RFE
    String date();      // Date RFE was implemented
}

Пример 9.6.1-2. Объявление маркерного интерфейса аннотаций

В следующем объявлении интерфейса аннотаций определен маркерный интерфейс аннотаций:

/**
 * An annotation with this type indicates that the 
 * specification of the annotated API element is 
 * preliminary and subject to change.
 */
@interface Preliminary {}

Пример 9.6.1-3. Объявления интерфейсов аннотаций с одним элементом

Соглашение о том, что интерфейс аннотации с одним элементом определяет элемент под названием value, проиллюстрировано в следующем объявлении интерфейса аннотации:

/**
 * Associates a copyright notice with the annotated API element.
 */
@interface Copyright {
    String value();
}

В следующем объявлении интерфейса аннотации определен интерфейс аннотации с одним элементом, у которого тип элемента — массив:

/**
 * Associates a list of endorsers with the annotated class.
 */
@interface Endorsers {
    String[] value();
}

В следующем объявлении интерфейса аннотации показан элемент типа Class, значение которого ограничено связанным подстановочным знаком:

interface Formatter {}

// Designates a formatter to pretty-print the annotated class
@interface PrettyPrinter {
    Class<? extends Formatter> value();
}

В следующем объявлении интерфейса аннотации содержится элемент, тип которого — тип интерфейса аннотации:

/**
 * Indicates the author of the annotated program element.
 */
@interface Author {
    Name value();
}
/**
 * A person's name.  This annotation interface is not 
 * designed to be used directly to annotate program elements, 
 * but to define elements of other annotation interfaces.
 */
@interface Name {
    String first();
    String last();
}

В грамматике объявлений интерфейсов аннотаций разрешены другие объявления членов помимо объявлений методов. Например, можно объявить вложенный класс перечисления для использования элементом интерфейса аннотации:

@interface Quality {
    enum Level { BAD, INDIFFERENT, GOOD }
    Level value();
}

9.6.2. Значения по умолчанию для элементов интерфейса аннотаций

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

DefaultValue:
default ElementValue

Следующие производства из §9.7.1 показаны здесь для удобства:

ElementValue:
ConditionalExpression
ElementValueArrayInitializer
Annotation
ElementValueArrayInitializer:
{ [ElementValueList] [,] }
ElementValueList:
ElementValue {, ElementValue}

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

Ошибка компиляции возникает, если тип элемента не соответствует (§9.7) заданному значению по умолчанию.

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

Пример 9.6.2-1. Объявление интерфейса аннотации со значениями по умолчанию

Вот уточнение интерфейса аннотации RequestForEnhancement из §9.6.1:

@interface RequestForEnhancement {
    int    id();       // No default - must be specified in 
                       // each annotation
    String synopsis(); // No default - must be specified in 
                       // each annotation
    String engineer()  default "[unassigned]";
    String date()      default "[unimplemented]";
}

9.6.3. Интерфейсы аннотаций, допускающие повторение

Интерфейс аннотации A является повторяемым, если его объявление (мета-)аннотировано аннотацией @Repeatable (§9.6.4.8), элемент value которой указывает на содержащий интерфейс аннотации для A.

Интерфейс аннотации AC является содержащим интерфейсом аннотации для A, если выполняются все следующие условия:

  1. AC объявляет метод value(), тип возвращаемого значения которого A[].

  2. Все методы, объявленные AC, кроме value(), имеют значение по умолчанию.

  3. AC сохраняется как минимум так же долго, как и A, где сохранение выражается явно или неявно аннотацией @Retention (§9.6.4.2). В частности:

    • Если срок хранения AC — java.lang.annotation.RetentionPolicy.SOURCE, то срок хранения A — java.lang.annotation.RetentionPolicy.SOURCE.

    • Если срок хранения AC — java.lang.annotation.RetentionPolicy.CLASS, то срок хранения A — либо java.lang.annotation.RetentionPolicy.CLASS, либо java.lang.annotation.RetentionPolicy.SOURCE.

    • Если срок хранения AC — java.lang.annotation.RetentionPolicy.RUNTIME, то срок хранения A — java.lang.annotation.RetentionPolicy.SOURCE, java.lang.annotation.RetentionPolicy.CLASS или java.lang.annotation.RetentionPolicy.RUNTIME.

  4. A применима как минимум к тем же видам элементов программы, что и AC (§9.6.4.1). В частности, если виды элементов программы, к которым применима A, обозначаются множеством m1, а виды элементов программы, к которым применима AC, обозначаются множеством m2, то каждый вид в m2 должен встречаться в m1, за исключением следующих случаев:

    • Если вид в m2 — java.lang.annotation.ElementType.ANNOTATION_TYPE, то хотя бы один из java.lang.annotation.ElementType.ANNOTATION_TYPE или java.lang.annotation.ElementType.TYPE или java.lang.annotation.ElementType.TYPE_USE должен встречаться в m1.

    • Если вид в m2 — java.lang.annotation.ElementType.TYPE, то хотя бы один из java.lang.annotation.ElementType.TYPE или java.lang.annotation.ElementType.TYPE_USE должен встречаться в m1.

    • Если вид в m2 — java.lang.annotation.ElementType.TYPE_PARAMETER, то хотя бы один из java.lang.annotation.ElementType.TYPE_PARAMETER или java.lang.annotation.ElementType.TYPE_USE должен встречаться в m1.

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

  5. Если объявление A содержит (мета-)аннотацию, соответствующую java.lang.annotation.Documented, то объявление AC должно содержать (мета-)аннотацию, соответствующую java.lang.annotation.Documented.

    Обратите внимание, что AC может быть @Documented, в то время как A — нет @Documented.

  6. Если объявление A содержит (мета-)аннотацию, соответствующую java.lang.annotation.Inherited, то объявление AC должно содержать (мета-)аннотацию, соответствующую java.lang.annotation.Inherited.

    Обратите внимание, что AC может быть @Inherited, в то время как A — нет @Inherited.

Если интерфейс аннотации A (мета-)аннотирован аннотацией @Repeatable, элемент value которой указывает на тип, не являющийся содержащим интерфейсом аннотации для A, возникает ошибка на этапе компиляции.

Пример 9.6.3-1. Некорректный содержащий интерфейс аннотации

Рассмотрим следующие объявления:

import java.lang.annotation.Repeatable;

@Repeatable(FooContainer.class)
@interface Foo {}

@interface FooContainer { Object[] value(); }

Компиляция объявления Foo приводит к ошибке на этапе компиляции, так как Foo пытается указать FooContainer в качестве содержащего интерфейса аннотации, но FooContainer на самом деле не является содержащим интерфейсом аннотации для Foo. (Тип возврата FooContainer.value() не равен Foo[].)


Аннотация @Repeatable не может быть повторённой, поэтому только один содержащий интерфейс аннотации может быть указан интерфейсом аннотации, допускающим повторение.

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

Интерфейс аннотации может быть содержащим интерфейсом аннотации для не более чем одного интерфейса аннотации.

Это подразумевается требованием, что если объявление интерфейса аннотации A указывает содержащий интерфейс аннотации AC, то метод value() AC имеет тип возвращаемого значения, включающий A, а именно A[].

Интерфейс аннотации не может указывать сам себя в качестве своего содержащего интерфейса аннотации.

Это подразумевается требованием к методу value() содержащего интерфейса аннотации. В частности, если интерфейс аннотации A указывает сам себя (через @Repeatable) в качестве своего содержащего интерфейса аннотации, то тип возврата метода value() A должен быть A[]; но это приведёт к ошибке на этапе компиляции, так как интерфейс аннотации не может ссылаться на себя в своих элементах (§9.6.1). В более общем смысле, два интерфейса аннотации не могут указывать друг друга в качестве своих содержащих интерфейсов аннотации, так как циклические объявления интерфейсов аннотаций недопустимы.

Интерфейс аннотации AC может быть содержащим интерфейсом аннотации для какого-то интерфейса аннотации A, одновременно имея свой собственный содержащий интерфейс аннотации SC. То есть, содержащий интерфейс аннотации может сам быть интерфейсом аннотации, допускающим повторение.

Пример 9.6.3-2. Ограничение повторения аннотаций

Аннотация, интерфейс объявления которой указывает на цель java.lang.annotation.ElementType.TYPE, может появляться как минимум в таком же количестве мест, что и аннотация, интерфейс объявления которой указывает на цель java.lang.annotation.ElementType.ANNOTATION_TYPE. Например, при следующих объявлениях интерфейсов аннотаций с возможностью повторения и содержащих аннотации:

import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Repeatable(FooContainer.class)
@interface Foo {}

@Target(ElementType.ANNOTATION_TYPE)
@interface FooContainer {
    Foo[] value();
}

@Foo может появляться на любом объявлении класса или интерфейса, в то время как @FooContainer может появляться только на объявлениях интерфейсов аннотаций. Поэтому следующее объявление интерфейса аннотации является допустимым:

@Foo @Foo
@interface Anno {}

в то время как следующее объявление интерфейса недопустимо:

@Foo @Foo
interface Intf {}

Более широко, если Foo — это интерфейс аннотации с возможностью повторения, а FooContainer — это интерфейс содержащей аннотации, тогда:

  • Если Foo не имеет мета-аннотацию @Target, а FooContainer не имеет мета-аннотацию @Target, то @Foo может повторяться на любом элементе программы, поддерживающем аннотации.

  • Если Foo не имеет мета-аннотацию @Target, но FooContainer имеет мета-аннотацию @Target, то @Foo может повторяться только на элементах программы, где может появляться @FooContainer.

  • Если Foo имеет мета-аннотацию @Target, то по мнению разработчиков языка программирования Java, FooContainer должно быть объявлено с учетом применимости Foo. В частности, виды элементов программы, где может появляться FooContainer, должны логически совпадать или быть подмножеством видов, которые применим для Foo.

    Например, если Foo применим к объявлениям полей и методов, то FooContainer может законно служить интерфейсом содержащей аннотации для Foo, если FooContainer применим только к объявлениям полей (что предотвратит повторение @Foo на объявлениях методов). Но если FooContainer применим только к объявлениям формальных параметров, то FooContainer был плохим выбором интерфейса содержащей аннотации для Foo, потому что @FooContainer не может быть неявно объявлено на некоторых элементах программы, где @Foo повторяется.

    Аналогично, если Foo применим к объявлениям полей и методов, то FooContainer не может законно служить интерфейсом содержащей аннотации для Foo, если FooContainer применим к объявлениям полей и параметров. Хотя было бы возможно взять пересечение элементов программы и сделать Foo повторяемым только для объявлений полей, наличие дополнительных элементов программы для FooContainer указывает на то, что FooContainer не был разработан как интерфейс содержащей аннотации для Foo. Поэтому для Foo было бы опасно положиться на него.


Пример 9.6.3-3. Интерфейс содержащей аннотации с возможностью повторения

Следующие объявления допустимы:

import java.lang.annotation.Repeatable;

// Foo: Repeatable annotation interface
@Repeatable(FooContainer.class)
@interface Foo { int value(); }

// FooContainer: Containing annotation interface of Foo
// Also a repeatable annotation interface itself
@Repeatable(FooContainerContainer.class)
@interface FooContainer { Foo[] value(); }

// FooContainerContainer: Containing annotation interface
// of FooContainer
@interface FooContainerContainer { FooContainer[] value(); }

Таким образом, аннотация, интерфейс которой является интерфейсом содержащей аннотации, может сама повторяться:

@FooContainer({@Foo(1)}) @FooContainer({@Foo(2)})
class Test {}

Интерфейс аннотации, который является как повторяемым, так и содержащим, подчиняется правилам смешивания аннотаций интерфейса с возможностью повторения с аннотациями содержащего интерфейса (§9.7.5). Например, нельзя написать несколько аннотаций @Foo рядом с несколькими аннотациями @FooContainer, также нельзя написать несколько аннотаций @FooContainer рядом с несколькими аннотациями @FooContainerContainer. Однако, если интерфейс аннотации FooContainerContainer сам является повторяемым, то можно написать несколько аннотаций @Foo рядом с несколькими аннотациями @FooContainerContainer.


9.6.4. Предопределённые интерфейсы аннотаций

В API Java SE Platform определены несколько интерфейсов аннотаций. Некоторые из предопределённых интерфейсов аннотаций имеют специальную семантику в языке программирования Java и требуют специального поведения со стороны компилятора Java, как указано в данном разделе. В данном разделе не приводится полное описание предопределённых интерфейсов аннотаций; для получения полной информации следует обратиться к документации API Java SE Platform (§1.4).

9.6.4.1. @Target

Аннотация типа java.lang.annotation.Target используется в объявлении интерфейса аннотации A для указания контекстов, в которых A является применимым. java.lang.annotation.Target содержит единственный элемент, value, типа java.lang.annotation.ElementType[], для указания контекстов.

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

Существует десять контекстов объявления, каждый из которых соответствует константе перечисления java.lang.annotation.ElementType:

  1. Объявления модулей (§7.7)

    Соответствует java.lang.annotation.ElementType.MODULE

  2. Объявления пакетов (§7.4.1)

    Соответствует java.lang.annotation.ElementType.PACKAGE

  3. Объявления классов (включая объявления перечислений и записей) и объявления интерфейсов (включая объявления интерфейсов аннотаций) (§8.1.1, §8.5, §8.9, §8.10, §9.1.1, §9.5, §9.6)

    Соответствует java.lang.annotation.ElementType.TYPE

    Кроме того, объявления интерфейсов аннотаций соответствуют java.lang.annotation.ElementType.ANNOTATION_TYPE

  4. Объявления методов (включая элементы интерфейсов аннотаций) (§8.4.3, §9.4, §9.6.1)

    Соответствует java.lang.annotation.ElementType.METHOD

  5. Объявления конструкторов (§8.8.3)

    Соответствует java.lang.annotation.ElementType.CONSTRUCTOR

  6. Объявления параметров типа для обобщённых классов, интерфейсов, методов и конструкторов (§8.1.2, §9.1.2, §8.4.4, §8.8.4)

    Соответствует java.lang.annotation.ElementType.TYPE_PARAMETER

  7. Объявления полей (включая константы перечислений) (§8.3.1, §9.3, §8.9.1)

    Соответствует java.lang.annotation.ElementType.FIELD

  8. Объявления формальных и исключающих параметров (§8.4.1, §9.4, §14.20)

    Соответствует java.lang.annotation.ElementType.PARAMETER

  9. Объявления локальных переменных в операторах (§14.4.2, §14.14.1, §14.14.2, §14.20.3) и в шаблонах (§14.30.1)

    Соответствует java.lang.annotation.ElementType.LOCAL_VARIABLE

  10. Объявления компонентов записей (§8.10.1)

    Соответствует java.lang.annotation.ElementType.RECORD_COMPONENT

Существует 17 контекстов типов (§4.11), все они представлены константой перечисления TYPE_USE из java.lang.annotation.ElementType.

Ошибка компиляции, если та же константа перечисления встречается более одного раза в элементе value аннотации типа java.lang.annotation.Target.

Если аннотация типа java.lang.annotation.Target отсутствует в объявлении интерфейса аннотации A, то A применима во всех контекстах объявлений и ни в одном контексте типов.

9.6.4.2. @Retention

Аннотации могут присутствовать только в исходном коде или в двоичном представлении класса или интерфейса. Аннотация, присутствующая в двоичном представлении, может или не может быть доступна во время выполнения через библиотеки рефлексии Java SE Platform. Интерфейс аннотации java.lang.annotation.Retention используется для выбора между этими возможностями.

Если аннотация a соответствует интерфейсу аннотации A, и A имеет (мета-)аннотацию m, соответствующую java.lang.annotation.Retention, то:

  • Если m имеет элемент со значением java.lang.annotation.RetentionPolicy.SOURCE, то компилятор Java должен гарантировать, что a отсутствует в двоичном представлении класса или интерфейса, в котором появляется a.

  • Если m имеет элемент со значением java.lang.annotation.RetentionPolicy.CLASS или java.lang.annotation.RetentionPolicy.RUNTIME, то компилятор Java должен гарантировать, что a представлено в двоичном представлении класса или интерфейса, в котором появляется a, если a не аннотирует объявление локальной переменной или a не аннотирует объявление формального параметра лямбда-выражения.

    Аннотация в объявлении локальной переменной или в объявлении формального параметра лямбда-выражения никогда не сохраняется в двоичном представлении. Напротив, аннотация в типе локальной переменной или в типе формального параметра лямбда-выражения сохраняется в двоичном представлении, если интерфейс аннотации указывает соответствующую политику сохранения.

    Обратите внимание, что не является ошибкой, если интерфейс аннотации помечен (мета-)аннотациями @Target(java.lang.annotation.ElementType.LOCAL_VARIABLE) и @Retention(java.lang.annotation.RetentionPolicy.CLASS) или @Retention(java.lang.annotation.RetentionPolicy.RUNTIME).

    Если m имеет элемент со значением java.lang.annotation.RetentionPolicy.RUNTIME, то библиотеки рефлексии Java SE Platform должны сделать a доступным во время выполнения.

Если у A нет (мета-)аннотации, соответствующей java.lang.annotation.Retention, то компилятор Java должен рассматривать A так, как будто у неё есть (мета-)аннотация, соответствующая java.lang.annotation.Retention с элементом, значением которого является java.lang.annotation.RetentionPolicy.CLASS.

9.6.4.3. @Inherited

Интерфейс аннотации java.lang.annotation.Inherited используется для указания того, что аннотации на классе C, соответствующие данному интерфейсу аннотации, наследуются подклассами C.

9.6.4.4. @Override

Программисты иногда перегружают объявление метода, когда намереваются его переопределить, что приводит к скрытым проблемам. Интерфейс аннотаций Override поддерживает раннее обнаружение таких проблем.

Классический пример касается метода equals. Программисты пишут следующее в классе Foo:

public boolean equals(Foo that) { ... }

когда они намереваются написать:

public boolean equals(Object that) { ... }

Это вполне законно, но класс Foo наследует реализацию equals от Object, что может вызвать некоторые скрытые ошибки.

Если объявление метода в классе или интерфейсе Q аннотировано с помощью @Override, то одно из следующих трёх условий должно быть истинным, иначе произойдёт ошибка компиляции:

  • метод переопределяет метод из Q, объявленный в супертипе Q (§8.4.8.1, §9.4.1.1)

  • метод эквивалентен переопределению методу public из Object (§4.3.2, §8.4.2)

  • Q — это класс-запись (§8.10), и метод является методом-аксессором для компонента записи Q (§8.10.3)

Это поведение отличается от Java SE 5.0, где @Override вызывало ошибку компиляции только в том случае, если оно применялось к методу, реализующему метод из суперинтерфейса, который также не был присутствующим в суперклассе.

Положение об переопределении метода public из Object мотивировано использованием @Override в интерфейсе. Рассмотрим следующие объявления:

class Foo     { @Override public int hashCode() {..} }
interface Bar { @Override int hashCode(); }

Использование @Override в объявлении класса является законным в соответствии с первым положением, поскольку Foo.hashCode переопределяет метод Object.hashCode из интерфейса Foo.

Для объявления интерфейса следует учесть, что интерфейс имеет public abstract члены, которые соответствуют public членам Object (§9.2). Если интерфейс выбирает явное объявление этих членов (то есть объявление членов, эквивалентных переопределению public методов Object), то интерфейс считается переопределяющим их, и использование @Override разрешено.

Однако, рассмотрим интерфейс, который пытается использовать @Override для метода clone: (finalize также может быть использован в этом примере)

interface Quux { @Override Object clone(); }

Поскольку Object.clone не является public, в Quux нет неявно объявленного члена с именем clone. Поэтому явное объявление clone в Quux не считается "реализацией" какого-либо другого метода, и использование @Override является ошибочным. (Факт, что Quux.clone является public, не имеет значения.)

В противоположность этому, объявление класса, которое объявляет clone, просто переопределяет Object.clone, поэтому может использовать @Override:

class Beep { @Override protected Object clone() {..} }

Положение о классе-записи обусловлено особым значением @Override в объявлении записи. А именно, он может использоваться для указания того, что объявление метода является методом-аксессором для компонента записи. Рассмотрим следующее объявление записи:

record Roo(int x) {
    @Override
    public int x() {
        return Math.abs(x);
    }
}

Использование @Override для метода-аксессора int x() гарантирует, что если компонент записи x будет изменён или удалён, то соответствующий метод-аксессор также должен быть изменён или удалён.

9.6.4.5. @SuppressWarnings

Компиляторы Java всё чаще способны выдавать полезные предупреждения "в стиле линтера". Для поощрения использования таких предупреждений должен быть способ отключения предупреждения в части программы, когда программист знает, что предупреждение необоснованно.

Интерфейс аннотаций SuppressWarnings поддерживает управление программистом предупреждениями, которые иначе выдаёт компилятор Java. Он определяет один элемент — массив String.

Если объявление аннотировано с помощью @SuppressWarnings(value = {S1, ..., Sk}), то компилятор Java должен подавлять (т.е. не сообщать) любое предупреждение, указанное одним из S1 ... Sk, если это предупреждение было бы сгенерировано в результате аннотированного объявления или любой его части.

Язык программирования Java определяет четыре вида предупреждений, которые могут быть указаны с помощью @SuppressWarnings:

  • Предупреждения о неявных типах (§4.8, §5.1.6, §5.1.9, §8.4.1, §8.4.8.3, §15.12.4.2, §15.13.2, §15.27.3) указываются строкой "unchecked".

  • Предупреждения о устаревших методах (§9.6.4.6) указываются строкой "deprecation".

  • Предупреждения об удалённых методах (§9.6.4.6) указываются строкой "removal".

  • Предупреждения о предварительных версиях (§1.5) указываются строкой "preview".

Любая другая строка указывает нестандартное предупреждение. Компилятор Java должен игнорировать любую такую строку, которую он не распознаёт.

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

9.6.4.6. @Deprecated

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

Элемент программы устаревший — это модуль, класс, интерфейс, поле, метод или конструктор, объявление которого аннотировано с помощью @Deprecated. Способ, которым элемент программы устарел, зависит от значения элемента forRemoval аннотации:

  • Если forRemoval=false (по умолчанию), то элемент программы обычно устарел.

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

  • Если forRemoval=true, то элемент программы окончательно устарел.

    Элемент программы, окончательно устаревший, предполагается к удалению в будущей версии. Программисты должны прекратить его использование, или рискуют столкнуться с несовместимостью исходного и бинарного кода (§13.2) при обновлении до более новой версии.

Компилятор Java должен генерировать предупреждение об устаревании, когда используется обычно устаревший элемент программы (переопределён, вызван или упомянут по имени) в объявлении элемента программы (явном или неявном), за исключением следующих случаев:

  • Использование находится внутри объявления, которое само по себе устарело, будь то обычно или окончательно; или

  • Использование находится внутри объявления, которое аннотировано для подавления предупреждений об устаревании (§9.6.4.5); или

  • Объявление, где происходит использование, и объявление обычно устаревшего элемента программы оба находятся в одном внешнем классе; или

  • Использование находится в объявлении import, которое импортирует обычно устаревший класс, интерфейс или член; или

  • Использование находится в директиве exports или opens (§7.7.2).

Компилятор Java должен генерировать предупреждение об удалении, когда используется окончательно устаревший элемент программы (переопределён, вызван или упомянут по имени) в объявлении элемента программы (явном или неявном), за исключением следующих случаев:

  • Использование находится внутри объявления, которое аннотировано для подавления предупреждений об удалении (§9.6.4.5); или

  • Объявление, где происходит использование, и объявление окончательно устаревшего элемента программы оба находятся в одном внешнем классе; или

  • Использование находится в объявлении import, которое импортирует окончательно устаревший класс, интерфейс или член; или

  • Использование находится в директиве exports или opens.

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

Предупреждение об устаревании или предупреждение об удалении не генерируется, если:

  • используется локальная переменная или формальный параметр (ссылка по имени), даже если объявление локальной переменной или формального параметра аннотировано с помощью @Deprecated.

  • используется имя пакета (ссылка через полное имя типа, или объявление import, или директива exports или opens), даже если объявление пакета аннотировано с помощью @Deprecated.

  • имя модуля используется директивой qualified exports или opens, даже если объявление модуля-друга аннотировано с помощью @Deprecated.

Объявление модуля, которое экспортирует или открывает пакет, обычно контролируется тем же программистом или командой, которые контролируют объявление пакета. Поэтому мало пользы в предупреждении, что объявление пакета аннотировано с помощью @Deprecated, когда пакет экспортируется или открывается объявлением модуля. В отличие от этого, объявление модуля, которое экспортирует или открывает пакет дружественному модулю, обычно не контролируется тем же программистом или командой, которые контролируют дружественный модуль. Простое экспорт или открытие пакета не делает объявление модуля зависимым от дружественного модуля, поэтому мало смысла в предупреждении, если дружественный модуль устарел; программист объявления модуля почти всегда хотел бы подавить такое предупреждение.

Единственное неявное объявление, которое может вызвать предупреждение об устаревании или предупреждение об удалении, — это аннотация контейнера (§9.7.5). Иначе говоря, если T — это повторяющийся интерфейс аннотации, а TC — это содержащий интерфейс аннотации, и TC устарел, то повторение аннотации @T вызовет предупреждение. Предупреждение связано с неявной аннотацией контейнера @TC. Сильно не рекомендуется устаревать интерфейс содержащей аннотации без устаревания соответствующего повторяющегося интерфейса аннотации.

9.6.4.7. @SafeVarargs

Параметр с переменным числом аргументов с типом элемента, который нельзя реифицировать (§4.7), может вызвать загрязнение кучи (§4.12.2) и привести к предупреждениям о не проверяемых ошибках во время компиляции (§5.1.9). Однако такие предупреждения неинформативны, если тело метода с переменным числом аргументов корректно работает с параметром с переменным числом аргументов.

Интерфейс аннотации SafeVarargs, при использовании для аннотирования объявления метода или конструктора, заставляет программиста утверждать, что предотвращает компилятор Java от отчёта о не проверяемых предупреждениях для объявления или вызова метода или конструктора с переменным числом аргументов, в котором компилятор иначе это делал бы из-за того, что параметр с переменным числом аргументов имеет нереифицируемый тип элемента.

Аннотация @SafeVarargs имеет нелокальные последствия, поскольку она подавляет не проверенные предупреждения в выражениях вызова метода, а также предупреждение о не проверенных ошибках, относящееся к самому объявлению метода с переменным числом аргументов (§8.4.1). В отличие от этого, аннотация @SuppressWarnings("unchecked") имеет локальные последствия, так как подавляет только предупреждения о не проверенных ошибках, относящиеся к объявлению метода.

Каноническая цель для @SafeVarargs — это метод, например java.util.Collections.addAll, объявление которого начинается с:

public static <T> boolean
  addAll(Collection<? super T> c, T... elements)

Параметр с переменным числом аргументов имеет объявленный тип T[], который не реифицируемый. Однако метод по сути просто считывает из входного массива и добавляет элементы в коллекцию, что обе операции являются безопасными по отношению к массиву. Поэтому любые предупреждения о не проверенных ошибках во время компиляции в выражениях вызова метода для java.util.Collections.addAll, вероятно, ложные и неинформативные. Применение @SafeVarargs к объявлению метода предотвращает генерацию этих предупреждений о не проверенных ошибках в выражениях вызова метода.

Ошибка компиляции, если объявление метода или конструктора с фиксированным числом аргументов аннотировано аннотацией @SafeVarargs.

Ошибка компиляции, если объявление метода с переменным числом аргументов не аннотировано static, final или private, аннотировано аннотацией @SafeVarargs.

Так как @SafeVarargs применим только к методам static, final и/или private методам экземпляра и конструкторам, аннотация не может использоваться там, где происходит переопределение метода. Наследование аннотаций работает только для аннотаций на классах (а не на методах, интерфейсах или конструкторах), поэтому аннотация @SafeVarargs-стиля не может передаваться через методы экземпляров в классах или через интерфейсы.

9.6.4.8. @Repeatable

Интерфейс аннотации java.lang.annotation.Repeatable используется в объявлении повторяющегося интерфейса аннотации для указания его содержащего интерфейса аннотации (§9.6.3).

Обратите внимание, что мета-аннотация @Repeatable в объявлении A, указывающая на AC, недостаточно для того, чтобы AC стал содержащим интерфейсом аннотации для A. Есть много правил корректности для AC, чтобы он считался содержащим интерфейсом аннотации для A.

9.6.4.9. @FunctionalInterface

Интерфейс аннотаций FunctionalInterface используется для обозначения того, что интерфейс предназначен быть функциональным интерфейсом (§9.8). Это облегчает раннее обнаружение неподходящих объявлений методов, появляющихся в интерфейсе или унаследованных им, если интерфейс предназначен быть функциональным.

Если объявление интерфейса аннотировано с помощью @FunctionalInterface, но фактически не является функциональным интерфейсом, то это ошибка времени компиляции.

Поскольку некоторые интерфейсы являются функциональными по умолчанию, не обязательно или желательно, чтобы все объявления функциональных интерфейсов были аннотированы с помощью @FunctionalInterface.

9.7. Аннотации

Аннотация — это маркер, который связывает информацию с элементом программы, но не имеет эффекта во время выполнения. Аннотация обозначает конкретный экземпляр интерфейса аннотаций (§9.6) и обычно предоставляет значения для элементов этого интерфейса.

Существует три вида аннотаций. Первый вид является наиболее общим, в то время как другие виды — это просто сокращения для первого вида.

Аннотация:
Нормальная аннотация
Маркерная аннотация
Аннотация с одним элементом

Нормальные аннотации описаны в §9.7.1, маркерные аннотации в §9.7.2, а аннотации с одним элементом в §9.7.3. Аннотации могут появляться в различных синтаксических местах программы, как описано в §9.7.4. Количество аннотаций одного и того же интерфейса, которые могут появиться в одном месте, определяется объявлением интерфейса, как описано в §9.7.5.

9.7.1. Обычные аннотации

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

NormalAnnotation:
@ TypeName ( [СписокПарЭлементЗначение] )
СписокПарЭлементЗначение:
ПараЭлементЗначение {, ПараЭлементЗначение}
ПараЭлементЗначение:
Идентификатор = ЗначениеЭлемента
ЗначениеЭлемента:
УсловноеВыражение
ИнициализаторМассиваЗначенийЭлемента
Аннотация
ИнициализаторМассиваЗначенийЭлемента:
{ [СписокЗначенийЭлемента] [,] }
СписокЗначенийЭлемента:
ЗначениеЭлемента {, ЗначениеЭлемента}

Обратите внимание, что знак @ (@) является отдельным лексемой (§3.11). Разрешается вставлять пробелы между ним и TypeName, но это не рекомендуется в плане стиля.

TypeName указывает интерфейс аннотации, соответствующий аннотации. Аннотация считается «от» этого интерфейса.

TypeName должен указывать на доступный интерфейс аннотации (§6.6), иначе произойдет ошибка на этапе компиляции.

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

Тип возвращаемого значения этого метода определяет тип элемента пары элемент-значение.

Если тип элемента является типом массива, то не требуется использовать фигурные скобки для указания значения элемента в паре элемент-значение. Если значение элемента не является ИнициализаторМассиваЗначенийЭлемента, то значение массива, содержащее единственный элемент — это значение элемента, ассоциируется с элементом. Если значение элемента является ИнициализаторМассиваЗначенийЭлемента, то значение массива, представленное ИнициализаторМассиваЗначенийЭлемента, ассоциируется с элементом.

Ошибка компиляции произойдёт, если тип элемента не совместим со значением элемента. Тип элемента T совместим со значением элемента v тогда и только тогда, когда выполняется одно из следующих условий:

  • T — это тип массива E[], и выполняется одно из следующих условий:

    • Если v является УсловноеВыражение или Аннотация, то v совместимо с E; или

    • Если v является ИнициализаторМассиваЗначенийЭлемента, то каждое значение элемента, которое v содержит, совместимо с E.

      ИнициализаторМассиваЗначенийЭлемента похож на обычный инициализатор массива (§10.6), за исключением того, что ИнициализаторМассиваЗначенийЭлемента может синтаксически содержать аннотации, а также выражения и вложенные инициализаторы. Однако вложенные инициализаторы не являются семантически допустимыми в ИнициализаторМассиваЗначенийЭлемента, так как они никогда не совместимы с элементами типа массива в объявлениях интерфейсов аннотаций (вложенные типы массивов запрещены).

  • T — не тип массива, и тип v совместим с T по присваиванию (§5.2), и:

    • Если T — примитивный тип или String, то v является константным выражением (§15.29).

    • Если T — тип Class или вызов Class (§4.5), то v является литералом класса (§15.8.2).

    • Если T — тип перечисления (§8.9), то v является константой перечисления (§8.9.1).

    • v не является null.

Обратите внимание, что если T не является типом массива или интерфейсом аннотации, значение элемента должно быть УсловноеВыражение (§15.25). Использование УсловноеВыражение вместо более общей конструкции, например, Выражение, — это синтаксический трюк, призванный предотвратить использование выражений присваивания в качестве значений элементов. Поскольку выражение присваивания не является константным выражением, оно не может быть совместимым значением элемента для элемента примитивного типа или String.

Ошибка компиляции произойдёт, если обычная аннотация не содержит пары элемент-значение для каждого элемента соответствующего интерфейса аннотации, за исключением элементов с значениями по умолчанию.

Обычная аннотация может, но не обязана, содержать пары элемент-значение для элементов со значениями по умолчанию.

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

Аннотация на объявлении интерфейса аннотации называется мета-аннотацией.

Аннотация интерфейса A может появиться в качестве мета-аннотации в объявлении самого интерфейса A. Более общо, циклы в транзитивном замыкании отношения «аннотирует» разрешены.

Например, допустимо аннотировать объявление интерфейса аннотации S мета-аннотацией интерфейса T и аннотировать объявление T собственной мета-аннотацией интерфейса S. Предопределенные интерфейсы аннотаций (§9.6.4) содержат несколько таких циклов.

Пример 9.7.1-1. Обычные аннотации

Вот пример обычной аннотации, использующей интерфейс аннотации из §9.6.1:


@RequestForEnhancement(
    id       = 2868724,
    synopsis = "Provide time-travel functionality",
    engineer = "Mr. Peabody",
    date     = "4/1/2004"
)
public static void travelThroughTime(Date destination) { ... }

Вот пример обычной аннотации, которая использует значения по умолчанию, используя интерфейс аннотации из §9.6.2:


@RequestForEnhancement(
    id       = 4561414,
    synopsis = "Balance the federal budget"
)
public static void balanceFederalBudget() {
    throw new UnsupportedOperationException("Not implemented");
}


9.7.2. Аннотации-метки

Аннотация-метка — это сокращение, предназначенное для использования с интерфейсами аннотаций-меток (§9.6.1).

MarkerAnnotation:
@ TypeName

Это сокращение для обычной аннотации:

@TypeName()

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

Пример 9.7.2-1. Аннотации-метки

Вот пример, использующий интерфейс аннотации-метки Preliminary из §9.6.1:

@Preliminary public class TimeTravel { ... }

9.7.3. Аннотации с единственным элементом

Аннотация с единственным элементом — это сокращение, предназначенное для использования с интерфейсами аннотаций с единственным элементом (§9.6.1).

SingleElementAnnotation:
@ ТипИмени ( ЗначениеЭлемента )

Это сокращение для обычной аннотации:

@TypeName(value = ElementValue)

Разрешено использовать аннотации с единственным элементом для интерфейсов аннотаций с несколькими элементами, при условии, что один элемент имеет имя value, а все остальные элементы имеют значения по умолчанию (§9.6.2).

Пример 9.7.3-1. Аннотации с единственным элементом

Следующие аннотации используют интерфейсы аннотаций с единственным элементом из §9.6.1.

Вот пример аннотации с единственным элементом:


@Copyright("2002 Yoyodyne Propulsion Systems, Inc.")
public class OscillationOverthruster { ... }

Вот пример аннотации с единственным элементом, содержащей массив значений:


@Endorsers({"Children", "Unscrupulous dentists"})
public class Lollipop { ... }

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


@Endorsers("Epicurus")
public class Pleasure { ... }

Вот пример аннотации с единственным элементом, содержащей элемент типа Class, значение которого ограничено связанным универсальным параметром.


class GorgeousFormatter implements Formatter { ... }

@PrettyPrinter(GorgeousFormatter.class)
public class Petunia { ... }

// Illegal; String is not a subtype of Formatter
@PrettyPrinter(String.class)
public class Begonia { ... }

Вот пример аннотации с единственным элементом, содержащей обычную аннотацию:


@Author(@Name(first = "Joe", last = "Hacker"))
public class BitTwiddle { ... }

Вот пример аннотации с единственным элементом, использующей перечисление (enum), определённое внутри объявления интерфейса аннотации:


@Quality(Quality.Level.GOOD)
public class Karma { ... }


9.7.4. Где могут появляться аннотации

Аннотация объявления — это аннотация, которая применяется к объявлению и чей интерфейс аннотации применим в контексте объявления (§9.6.4.1), представленном этим объявлением; или аннотация, которая применяется к объявлению класса, интерфейса или параметра типа, и чей интерфейс аннотации применим в контекстах типов (§4.11).

Аннотация типа — это аннотация, которая применяется к типу (или любой его части) и чей интерфейс аннотации применим в контекстах типов.

Например, приведённое объявление поля:

@Foo int f;

@Foo является аннотацией объявления для f, если Foo мета-аннотирована интерфейсом @Target(ElementType.FIELD), и аннотацией типа для int, если Foo мета-аннотирована интерфейсом @Target(ElementType.TYPE_USE). Возможно, что @Foo одновременно является и аннотацией объявления, и аннотацией типа.

Аннотации типов могут применяться к типу массива или любому типу его компонента (§10.1). Например, предполагая, что A, B и C являются интерфейсами аннотаций, мета-аннотированными интерфейсом @Target(ElementType.TYPE_USE), тогда приведённое объявление поля:

@C int @A [] @B [] f;

@A применяется к типу массива int[][], @B применяется к его типу компонента int[], и @C применяется к типу элемента int. Для получения дополнительных примеров см. §10.2.

Важным свойством этого синтаксиса является то, что в двух объявлениях, отличающихся только количеством уровней массива, аннотации слева от типа относятся к одному и тому же типу. Например, @C применяется к типу int во всех следующих объявлениях:

@C int f;
@C int[] f;
@C int[][] f;

Хотя это не является обязательным, принято записывать аннотации объявления перед другими модификаторами и аннотации типа непосредственно перед типом, к которому они применяются.

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

  • Объявления методов (включая элементы интерфейсов аннотаций)

  • Объявления конструкторов

  • Объявления полей (включая константы перечислений)

  • Объявления параметров формальных и исключений

  • Объявления локальных переменных

  • Объявления компонентов записей

Грамматика языка программирования Java однозначно рассматривает аннотации в этих местах как модификаторы объявления (§8.3), но это чисто синтаксический вопрос. Применяется ли аннотация к объявлению или к типу объявляемой сущности — и таким образом, является ли аннотация аннотацией объявления или аннотацией типа — зависит от применимости интерфейса аннотации:

  • Если интерфейс аннотации применим в контексте объявления, соответствующего объявлению, и не применим в контекстах типов, то аннотация считается применимой только к объявлению.

  • Если интерфейс аннотации применим в контекстах типов и не применим в контексте объявления, соответствующем объявлению, то аннотация считается применимой только к типу, который находится ближе всего к аннотации.

  • Если интерфейс аннотации применим в контексте объявления, соответствующего объявлению, и в контекстах типов, то аннотация считается применимой как к объявлению, так и к типу, который находится ближе всего к аннотации.

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

  • Если аннотация появляется перед объявлением метода void или объявлением переменной, использующей var (§14.4, §15.27.1), то ближайшего типа нет. Если интерфейс аннотации считается применимым только к типу, который ближе всего к аннотации, возникает ошибка времени компиляции.

  • Если аннотация появляется перед объявлением конструктора, то ближайший тип — это тип вновь созданного объекта. Тип вновь созданного объекта — это полное квалифицированное имя типа, непосредственно содержащего объявление конструктора. В этом полном квалифицированном имени аннотация применяется к простому имени типа, указанному объявлением конструктора.

  • Во всех остальных случаях ближайший тип — это тип, написанный в исходном коде для объявляемой сущности; если этот тип является типом массива, то тип элемента считается ближайшим к аннотации.

    Например, в объявлении поля @Foo public static String f;, типом, который ближе всего к @Foo, является String. (Если тип объявления поля был написан как java.lang.String, то java.lang.String был бы типом, ближайшим к @Foo, а последующие правила запретили бы применение аннотации типа к имени пакета java.) В объявленной генерическом методе @Foo <T> int[] m() {...}, тип, написанный для объявляемой сущности, — это int[], поэтому @Foo применяется к типу элемента int.

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

Ошибка времени компиляции, если аннотация интерфейса A синтаксически является модификатором:

  • объявления модуля, но A неприменим к объявлениям модулей.

  • объявления пакета, но A неприменим к объявлениям пакетов.

  • объявления класса или интерфейса, но A неприменим к объявлениям типов или в контекстах типов; или

    объявления интерфейса аннотации, но A неприменим к объявлениям интерфейсов аннотаций или объявлениям типов или в контекстах типов.

  • объявления метода (включая элемент интерфейса аннотации), но A неприменим к объявлениям методов или в контекстах типов.

  • объявления конструктора, но A неприменим к объявлениям конструкторов или в контекстах типов.

  • объявления параметра типа генерического класса, интерфейса, метода или конструктора, но A неприменим к объявлениям параметров типов или в контекстах типов.

  • объявления поля (или константы перечисления), но A неприменим к объявлениям полей или в контекстах типов.

  • объявления формального или исключительного параметра, но A неприменим к объявлениям формальных и исключительных параметров или в контекстах типов.

  • объявления параметра получателя, но A неприменим в контекстах типов.

  • объявления локальной переменной в операторе или в шаблоне, но A неприменим к объявлениям локальных переменных или в контекстах типов.

  • объявления компонента записи, но A неприменим к объявлениям компонентов записей, объявлениям полей, объявлениям методов или объявлениям формальных и исключительных параметров, или в контекстах типов.

Шесть из этих одиннадцати пунктов упоминают "... или в контекстах типов", потому что они характеризуют шесть синтаксических мест, упомянутых ранее в этом разделе, где аннотация могла бы применяться к объявлению или типу. Кроме того, два из одиннадцати пунктов — для объявлений классов и интерфейсов и для объявлений параметров типов — упоминают "... или в контекстах типов", поскольку иногда удобно иметь возможность применить аннотацию, чей интерфейс мета-аннотирован @Target(ElementType.TYPE_USE) (следовательно, применима в контекстах типов) к объявлению класса, интерфейса или параметра типа.

Аннотация типа допустима, если оба следующих утверждения истинны:

  • Простое имя, к которому ближе всего расположена аннотация, классифицируется как TypeName, а не как PackageName.

  • Если простое имя, к которому ближе всего расположена аннотация, следует за "." и другим TypeName — то есть аннотация появляется как @Foo T.U — то U обозначает внутренний класс T.

Интуиция, лежащая в основе второго утверждения, заключается в том, что если Outer.this допустимо во вложенном классе, заключённом в Outer, то Outer может быть аннотировано, поскольку оно представляет тип некоторого объекта во время выполнения. С другой стороны, если Outer.this недопустимо — поскольку класс, в котором оно появляется, не имеет вложенного экземпляра Outer во время выполнения — то Outer не может быть аннотировано, поскольку оно логически просто имя, аналогично компонентам имени пакета в полном имени типа.

Например, в следующей программе невозможно написать A.this в теле B, так как у B нет лексически вложенных экземпляров. Поэтому невозможно применить @Foo к A в типе A.B, потому что A логически является просто именем, а не типом.


@Target(ElementType.TYPE_USE)
@interface Foo {}

class A {
    static class B {}
}

@Foo A.B x;  // Illegal 

С другой стороны, в следующей программе можно написать C.this в теле D. Поэтому можно применить @Foo к C в типе C.D, так как C представляет тип некоторого объекта во время выполнения.


@Target(ElementType.TYPE_USE)
@interface Foo {}

class Test {
    static class C {
        class D {}
    }

    @Foo C.D x;  // Legal 
}

Если аннотация интерфейса A применяется к внешнему уровню типа в контексте типа, а A не применима в контексте типа или контексте объявления (если таковой имеется), занимающем то же синтаксическое место, это ошибка времени компиляции.

Если аннотация интерфейса A применяется к части типа (то есть не к внешнему уровню) в контексте типа, а A не применима в контексте типа, это ошибка времени компиляции.

Если аннотация интерфейса A применяется к типу (или любой части типа) в контексте типа, и A применима в контекстах типов, но аннотация недопустима, это ошибка времени компиляции.

Например, предположим интерфейс аннотации TA, который мета-аннотирован только @Target(ElementType.TYPE_USE). Термины @TA java.lang.Object и java.@TA lang.Object недопустимы, потому что простое имя, к которому @TA ближе всего, классифицируется как имя пакета. С другой стороны, java.lang.@TA Object допустимо.

Обратите внимание, что недопустимые термины недопустимы «везде». Запрет на аннотирование имён пакетов распространяется на места, которые являются только контекстами типов, такие как class ... extends @TA java.lang.Object {...}, и на места, которые являются и контекстами объявлений, и контекстами типов, такие как @TA java.lang.Object f;. (Не существует мест, которые являются только контекстами объявления, где имя пакета может быть аннотировано, поскольку объявления пакетов, классов, интерфейсов и параметров типа вводят только простые имена.)

Если TA дополнительно мета-аннотирован @Target(ElementType.FIELD), то термин @TA java.lang.Object допустим в местах, которые являются и контекстами объявления, и контекстами типа, таких как объявление поля @TA java.lang.Object f;. Здесь @TA считается применимым к объявлению f (а не к типу java.lang.Object), потому что TA применимо в контексте объявления поля.

9.7.5. Несколько аннотаций одного и того же интерфейса

Если в контексте объявления или контексте типа появляются несколько аннотаций одного и того же интерфейса A, это ошибка времени компиляции, если A не повторяющаяся (§9.6.3), и A и содержащий интерфейс аннотации A применимы в контексте объявления или контексте типа (§9.6.4.1).

Принято, хотя и не обязательно, чтобы несколько аннотаций одного и того же интерфейса появлялись последовательно.

Если в контексте объявления или контексте типа есть несколько аннотаций повторяющегося интерфейса аннотаций A, то считается, что в контексте нет явно указанных аннотаций интерфейса A, и есть одна неявно объявленная аннотация содержащего интерфейса аннотации A.

Неявно объявленная аннотация называется контейнерной аннотацией, а несколько аннотаций интерфейса A, которые появились в контексте, называются базовыми аннотациями. Элементы (с типом массива) элемента value контейнерной аннотации — это все базовые аннотации в порядке следования слева направо, в котором они появились в контексте.

Если в контексте объявления или контексте типа есть несколько аннотаций повторяющегося интерфейса аннотаций A, и есть какие-либо аннотации содержащего интерфейса аннотации A, это ошибка времени компиляции.

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


@Foo(0) @Foo(1) @FooContainer({@Foo(2)})
class A {}

Если этот код был бы допустим, то потребовалось бы несколько уровней вложения: сначала базовые аннотации интерфейса Foo содержались бы в неявно объявленной контейнерной аннотации интерфейса FooContainer, затем эта аннотация и явно объявленная аннотация интерфейса FooContainer содержались бы в ещё одной неявно объявленной аннотации. Эта сложность нежелательна с точки зрения разработчиков языка программирования Java. Другой подход, рассматривающий базовые аннотации интерфейса Foo так, как будто они произошли рядом с @Foo(2) в явной @FooContainer аннотации, нежелателен, поскольку он может изменить то, как рефлексивные программы интерпретируют @FooContainer аннотацию.

Если в контексте объявления или контексте типа есть одна аннотация повторяющегося интерфейса аннотаций A и несколько аннотаций содержащего интерфейса аннотации A, это ошибка времени компиляции.

Это правило разработано для того, чтобы позволить следующий код:


@Foo(1) @FooContainer({@Foo(2)})
class A {}

При наличии только одной базовой аннотации повторяющегося интерфейса аннотаций Foo, контейнерная аннотация не объявляется неявно, даже если FooContainer — это содержащий интерфейс аннотации Foo. Однако повторение аннотации интерфейса FooContainer, как в:


@Foo(1) @FooContainer({@Foo(2)}) @FooContainer({@Foo(3)})
class A {}

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

9.8. Функциональные интерфейсы

Функциональный интерфейс — это интерфейс, который не объявлен sealed и имеет только один abstract метод (кроме методов Object), и таким образом представляет собой контракт одной функции. Этот «один» метод может представлять собой несколько abstract методов с эквивалентными по переопределению сигнатурами, унаследованными от суперинтерфейсов; в этом случае унаследованные методы логически представляют собой один метод.

Для интерфейса I, который не объявлен sealed, пусть M будет набором abstract методов, которые являются членами I и не имеют одинаковой сигнатуры с любым public методом экземпляра класса Object (§4.3.2). Тогда I является функциональным интерфейсом, если существует метод m в M, для которого оба следующих утверждения истинны:

  • Подпись m является подсигнатурой (§8.4.2) для каждой подписи метода в M.

  • m является замещаемой по возвращаемому типу (§8.4.5) для каждого метода в M.

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

Определение функционального интерфейса исключает методы в интерфейсе, которые также являются public методами в Object. Это позволяет функционально обрабатывать интерфейс, подобный java.util.Comparator<T>, который объявляет несколько abstract методов, из которых только один является действительно «новым» - int compare(T,T). Другой - boolean equals(Object) - это явное объявление abstract метода, который в противном случае был бы неявно объявлен в интерфейсе (§9.2) и автоматически реализовывался каждым классом, который implements интерфейс.

Обратите внимание, что если не-public методы Object, такие как clone(), явно объявлены в интерфейсе как public, они не автоматически реализуются каждым классом, который implements интерфейс. Реализация, унаследованная от Object, является protected, в то время как метод интерфейса является public, поэтому единственный способ реализовать интерфейс заключается в том, чтобы класс переопределил не-public Object метод с public методом.

Пример 9.8-1. Функциональные интерфейсы

Простой пример функционального интерфейса:

interface Runnable {
    void run();
}

Следующий интерфейс не является функциональным, поскольку он не объявляет ничего, что не является уже членом Object:

interface NonFunc {
    boolean equals(Object obj);
}

Однако его подинтерфейс может быть функциональным, объявив abstract метод, который не является членом Object:

interface Func extends NonFunc {
    int compare(String o1, String o2);
}

Аналогично, всем известный интерфейс java.util.Comparator<T> является функциональным, так как он имеет один abstract не-Object метод:

interface Comparator<T> {
    boolean equals(Object obj);
    int compare(T o1, T o2);
}

Следующий интерфейс не является функциональным, поскольку, хотя он объявляет только один abstract метод, который не является членом Object, он объявляет два abstract метода, которые не являются public членами Object:

interface Foo {
    int m();
    Object clone();
}

Пример 9.8-2. Функциональные интерфейсы и стирание

В следующей иерархии интерфейсов Z является функциональным интерфейсом, так как, хотя он наследует два abstract метода, которые не являются членами Object, у них одинаковая подпись, поэтому унаследованные методы логически представляют собой один метод:

interface X { int m(Iterable<String> arg); }
interface Y { int m(Iterable<String> arg); }
interface Z extends X, Y {}

Аналогично, Z является функциональным интерфейсом в следующей иерархии интерфейсов, потому что Y.m является подсигнатурой X.m и подменяемой по возвращаемому типу для X.m:

interface X { Iterable m(Iterable<String> arg); }
interface Y { Iterable<String> m(Iterable arg); }
interface Z extends X, Y {}

Определение функционального интерфейса учитывает тот факт, что интерфейс не может иметь два члена, которые не являются подсигнатурами друг друга, но имеют одинаковую стирание (§9.4.1.2). Таким образом, в следующих трех иерархиях интерфейсов, где Z вызывает ошибку компиляции, Z не является функциональным интерфейсом: (потому что ни один из его abstract членов не является подсигнатурой всех других abstract членов)

interface X { int m(Iterable<String> arg); }
interface Y { int m(Iterable<Integer> arg); }
interface Z extends X, Y {}

interface X { int m(Iterable<String> arg, Class c); }
interface Y { int m(Iterable arg, Class<?> c); }
interface Z extends X, Y {}

interface X<T> { void m(T arg); }
interface Y<T> { void m(T arg); }
interface Z<A, B> extends X<A>, Y<B> {}

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

interface X { long m(); }
interface Y { int  m(); }
interface Z extends X, Y {}

В следующем примере объявления Foo<T,N> и Bar являются допустимыми: в каждом из них методы, называемые m, не являются подсигнатурами друг друга, но имеют разные стирания. Тем не менее, тот факт, что методы в каждом не являются подсигнатурами, означает, что Foo<T,N> и Bar не являются функциональными интерфейсами. Однако Baz является функциональным интерфейсом, потому что методы, которые он наследует от Foo<Integer,Integer>, имеют одинаковую подпись и, таким образом, логически представляют собой один метод.

interface Foo<T, N extends Number> {
    void m(T arg);
    void m(N arg);
}
interface Bar extends Foo<String, Integer> {}
interface Baz extends Foo<Integer, Integer> {}

Наконец, следующие примеры демонстрируют те же правила, что и выше, но с дженериками:

interface Exec { <T> T execute(Action<T> a); }
  // Functional

interface X { <T> T execute(Action<T> a); }
interface Y { <S> S execute(Action<S> a); }
interface Exec extends X, Y {}
  // Functional: signatures are logically "the same"

interface X { <T>   T execute(Action<T> a); }
interface Y { <S,T> S execute(Action<S> a); }
interface Exec extends X, Y {}
  // Error: different signatures, same erasure

Пример 9.8-3. Дженеризованные функциональные интерфейсы

Функциональные интерфейсы могут быть дженеризованными, например, java.util.function.Predicate<T>. Такой функциональный интерфейс может быть параметризован таким образом, что создает различные abstract методы - то есть несколько методов, которые нельзя законно переопределить с помощью одного объявления. Например:

interface I    { Object m(Class c); }
interface J<S> { S m(Class<?> c); }
interface K<T> { T m(Class<?> c); }
interface Functional<S,T> extends I, J<S>, K<T> {}

Functional<S,T> является функциональным интерфейсом - I.m подменяем по возвращаемому типу для J.m и K.m - но тип функционального интерфейса Functional<String,Integer> явно не может быть реализован одним методом. Однако другие параметризации Functional<S,T>, которые являются типами функциональных интерфейсов, возможны.


Объявление функционального интерфейса позволяет использовать тип функционального интерфейса в программе. Существует четыре типа функциональных интерфейсных типов:

  • Тип необобщенного (§6.1) функционального интерфейса

  • Параметризованный тип, являющийся параметризацией (§4.5) обобщенного функционального интерфейса

  • Необработанный тип (§4.8) обобщенного функционального интерфейса

  • Тип пересечения (§4.9), который индуцирует понятийный функциональный интерфейс

В особых случаях полезно рассматривать тип пересечения как тип функционального интерфейса. Как правило, это будет выглядеть как пересечение типа функционального интерфейса с одним или несколькими типами маркерных интерфейсов, например, Runnable & java.io.Serializable. Такое пересечение может использоваться в приведениях типов (§15.16), которые заставляют лямбда-выражение соответствовать определенному типу. Если один из типов интерфейса в пересечении является java.io.Serializable, срабатывает специальная поддержка выполнения для сериализации (§15.27.4).

9.9. Типы функций

Тип функции функционального интерфейса I — это тип метода (§8.2), который может быть использован для переопределения (§8.4.8) метода(ов) интерфейса I.

Пусть M — это множество abstract методов, определённых для I. Тип функции I состоит из следующего:

  • Параметры типа, формальные типы параметров и возвращаемый тип:

    Пусть m — метод в M с:

    1. сигнатурой, являющейся подсигнатурой сигнатуры каждого метода в M; и

    2. возвращаемым типом R (возможно void), где либо R совпадает с возвращаемым типом каждого метода в M, либо R — это ссылочный тип и является подтипом возвращаемого типа каждого метода в M (после адаптации для любых параметров типа (§8.4.4), если у двух методов одинаковая сигнатура).

    Если такого метода не существует, то пусть m — метод в M с:

    1. сигнатурой, являющейся подсигнатурой сигнатуры каждого метода в M; и

    2. возвращаемым типом, таким что m подставляется возвращаемым типом (§8.4.5) для каждого метода в M.

    Параметры типа, формальные типы параметров и возвращаемый тип типа функции задаются m.

  • throws описания:

    throws описание типа функции получено из throws описаний методов в M следующим образом:

    1. Если тип функции является обобщённым, то throws описания сначала адаптируются к параметрам типа типа функции (§8.4.4).

      Если тип функции не обобщённый, но хотя бы один метод в M обобщённый, то throws описания сначала стираются.

    2. Затем throws описание типа функции включает каждый тип E, который удовлетворяет следующим ограничениям:

      • E указан в одном из throws описаний.

      • Для каждого throws описания, E является подтипом какого-то типа, указанного в этом описании.

Когда некоторые возвращаемые типы в M являются сырыми, а другие — нет, определение типа функции пытается выбрать наиболее специфический тип, если это возможно. Например, если возвращаемые типы являются LinkedList и LinkedList<String>, то последний сразу выбирается в качестве возвращаемого типа типа функции. Когда нет наиболее специфического типа, определение компенсирует это, найдя наиболее подставляемый возвращаемый тип. Например, если есть третий возвращаемый тип List<?>, то не верно, что один из возвращаемых типов является подтипом каждого другого (так как сырой LinkedList не является подтипом List<?>); вместо этого выбирается LinkedList<String> в качестве возвращаемого типа типа функции, потому что он подставляется возвращаемым типом для обоих LinkedList и List<?>.

Цель, определяющая определение типов исключений, выбрасываемых типом функции, заключается в обеспечении инварианта, что метод с полученным throws описанием может переопределять каждый abstract метод функционального интерфейса. Согласно §8.4.6, это означает, что тип функции не может выбрасывать "больше" исключений, чем любой один метод в множестве M, поэтому мы ищем как можно больше типов исключений, которые "покрываются" описанием throws каждого метода.

Тип функции типа функционального интерфейса задаётся следующим образом:

  • Тип функции типа необобщённого функционального интерфейса I — это просто тип функции функционального интерфейса I, как определено выше.

  • Тип функции параметризованного типа функционального интерфейса I<A1...An>, где A1...An — типы, а соответствующие параметры типа I — P1...Pn, выводится путём применения подстановки [P1:=A1, ..., Pn:=An] к типу функции обобщённого функционального интерфейса I<P1...Pn>.

  • Тип функции параметризованного типа функционального интерфейса I<A1...An>, где один или несколько из A1...An являются джойлвайдами, — это тип функции не-джойлвайд параметризации I, I<T1...Tn>. Не-джойлвайд параметризация определяется следующим образом.

    Пусть P1...Pn — параметры типа I с соответствующими ограничениями B1...Bn. Для всех i (1 ≤ i ≤ n), Ti определяется в зависимости от формы Ai:

    • Если Ai — тип, то Ti = Ai.

    • Если Ai — джойлвайд, и соответствующее ограничение параметра типа Bi упоминает один из P1...Pn, то Ti не определено, и типа функции нет.

    • В противном случае:

      • Если Ai — неограниченный джойлвайд ?, то Ti = Bi.

      • Если Ai — верхне-ограниченный джойлвайд ? extends Ui, то Ti = glb(Ui, Bi) (§5.1.10).

      • Если Ai — нижне-ограниченный джойлвайд ? super Li, то Ti = Li.

  • Тип функции сырого типа обобщённого функционального интерфейса I<...> — это стирание типа функции обобщённого функционального интерфейса I<...>.

  • Тип функции типа пересечения, который порождает воображаемый функциональный интерфейс, — это тип функции воображаемого функционального интерфейса.

Пример 9.9-1. Типы функций

Даны следующие интерфейсы:

interface X { void m() throws IOException; }
interface Y { void m() throws EOFException; }
interface Z { void m() throws ClassNotFoundException; }

тип функции:

interface XY extends X, Y {}

равен:

()->void throws EOFException

в то время как тип функции:

interface XYZ extends X, Y, Z {}

равен:

()->void (throws nothing)

Даны следующие интерфейсы:

interface A {
    List<String> foo(List<String> arg)
      throws IOException, SQLTransientException;
}
interface B {
    List foo(List<String> arg)
      throws EOFException, SQLException, TimeoutException;
}
interface C {
    List foo(List arg) throws Exception;
}

тип функции:

interface D extends A, B {}

равен:

(List<String>)->List<String>
  throws EOFException, SQLTransientException

в то время как тип функции:

interface E extends A, B, C {}

равен:

(List)->List throws EOFException, SQLTransientException

Тип функции функционального интерфейса определяется не однозначно: хотя сигнатуры в M "одинаковы", они могут быть синтаксически различными (HashMap.Entry и Map.Entry, например); тип возвращаемого значения может быть подтипом любого другого типа возвращаемого значения, но могут быть и другие типы возвращаемых значений, которые также являются подтипами (List<?> и List<? extends Object>, например); и порядок типов, выбрасываемых в исключениях, не определён. Эти различия тонкие, но иногда они могут быть важными. Однако, типы функций не используются в языке программирования Java таким образом, чтобы неопределённость имела значение. Обратите внимание, что тип возвращаемого значения и throws клауза "самого специфичного метода" также определяются не однозначно, когда есть несколько abstract методов (§15.12.2.5).

Когда обобщённый функциональный интерфейс параметризуется маркерами подстановок, существует много различных экземпляров, которые могли бы удовлетворить маркер подстановки и произвести разные типы функций. Например, каждый из Predicate<Integer> (тип функции Integer -> boolean), Predicate<Number> (тип функции Number -> boolean) и Predicate<Object> (тип функции Object -> boolean) является Predicate<? super Integer>. Иногда из контекста, например, типов параметров лямбда-выражения, можно узнать, какой тип функции подразумевается (§15.27.3). В других случаях необходимо выбрать один; в этих случаях используются ограничения. (Эта простая стратегия не может гарантировать, что полученный тип будет удовлетворять определённым сложным ограничениям, поэтому не все сложные случаи поддерживаются.)

Пример 9.9-2. Обобщённые типы функций

Тип функции может быть обобщённым, поскольку abstract метод функционального интерфейса может быть обобщённым. Например, в следующей иерархии интерфейсов:

interface G1 {
    <E extends Exception> Object m() throws E;
}
interface G2 {
    <F extends Exception> String m() throws Exception;
}
interface G extends G1, G2 {}

тип функции G равен:

<F extends Exception> ()->String throws F

Обобщённый тип функции для функционального интерфейса может быть реализован выражением ссылки на метод (§15.13), но не лямбда-выражением (§15.27), поскольку нет синтаксиса для обобщённых лямбда-выражений.


© Oracle and/or its affiliates. All rights reserved.
Licensed under the Oracle Technology Network License Agreement.

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API