Spec-Zone.ru › Java Language Specification 24

Глава 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 ссылается на k само по себе.


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

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

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

ЗаголовокМетода:
Результат ДеклараторМетода [Исключения]
ПараметрыТипов {Аннотация} Результат ДеклараторМетода [Исключения]
Результат:
ТипБезАннотаций
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:
Объявление элемента интерфейса аннотации
Объявление константы
Объявление класса
Объявление интерфейса
;
AnnotationInterfaceElementDeclaration:
{Модификатор элемента интерфейса аннотации} Тип без аннотаций Идентификатор ( ) [Размеры] [Значение по умолчанию] ;
AnnotationInterfaceElementModifier:
(одно из)
Аннотация public
abstract

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

Dims:
{Аннотация} [ ] {{Аннотация} [ ]}

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

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

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

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

  • 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 Значение элемента

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

ElementValue:
Условное выражение
Инициализатор массива значений элементов
Аннотация
ElementValueArrayInitializer:
{ [Список значений элементов] [,] }
ElementValueList:
Значение элемента {, Значение элемента}

Обратите внимание, что элемент интерфейса аннотации, для которого указано значение по умолчанию, не является методом по умолчанию (§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 использует @Repeatable для попытки указать 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 определены несколько интерфейсов аннотаций. Некоторые из предварительно определённых интерфейсов аннотаций имеют специальную семантику в языке программирования Java и требуют специального поведения со стороны компилятора Java, как указано в данном разделе. В данном разделе не приводится полное описание предварительно определённых интерфейсов аннотаций, для этого следует обратиться к документации API платформы Java SE (§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. Интерфейс аннотаций 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 должны сделать 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, нет члена, называемого clone, неявно объявленного в классе Quux. Поэтому явное объявление 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.

  • имя модуля используется в директиве квалифицированного 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 совместим для присваивания (§5.2) с T, и:

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

    • Если T — тип перечисления или вызов 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 { ... }

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


@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) (следовательно, применима в контекстах типов) к объявлению класса, интерфейса или параметра типа.

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

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

  • Если простое имя, к которому ближе всего расположена аннотация, за которым следует "." и другое ИмяТипа — то есть, аннотация появляется как @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