Spec-Zone.ru › Java Language Specification 8

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

Оглавление

9.1. Объявления интерфейсов
9.1.1. Модификаторы интерфейсов
9.1.1.1. abstract Интерфейсы
9.1.1.2. strictfp Интерфейсы
9.1.2. Обобщенные интерфейсы и параметры типов
9.1.3. Родительские и дочерние интерфейсы
9.1.4. Тело интерфейса и объявления членов
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 методов. Интерфейсы не могут быть непосредственно инстанцированы.

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

Интерфейс верхнего уровня — это интерфейс, который не является вложенным интерфейсом.

Мы различаем два вида интерфейсов — обычные интерфейсы и типы аннотаций.

В этой главе рассматриваются общие семантические характеристики всех интерфейсов — обычных интерфейсов, как верхнего уровня (§7.6), так и вложенных (§8.5, §9.5), и типов аннотаций (§9.6). Подробности, относящиеся к конкретным видам интерфейсов, обсуждаются в разделах, посвященных этим конструкциям.

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

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

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

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

END_OF_DOCUMENT_MARKER

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

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

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

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

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

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

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

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

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

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

Модификатор доступа public (§6.6) относится ко всем видам объявлений интерфейсов.

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

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

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

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

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

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

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

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

Эффект модификатора strictfp заключается в том, что все float или double выражения внутри объявления интерфейса будут явно FP-строгими (§15.4).

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

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.

Ссылка на параметр типа интерфейса I в любом месте в объявлении поля или члена типа I является ошибкой компиляции.

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

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

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

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

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

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

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

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

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

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

Для объявления (возможно, обобщенного) интерфейса I<F1,...,Fn> (n ≥ 0), прямые суперинтерфейсы типа интерфейса I<F1,...,Fn> — это типы, указанные в extends части объявления I, если такая extends часть присутствует.

Для объявления обобщенного интерфейса I<F1,...,Fn> (n > 0), прямые суперинтерфейсы параметризованного типа интерфейса I<T1,...,Tn>, где Ti (1 ≤ i ≤ n) — это тип, — это все типы J<U1 θ,...,Uk θ>, где J<U1,...,Uk> является прямым суперинтерфейсом I<F1,...,Fn> и θ — это подстановка [F1:=T1,...,Fn:=Tn].

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

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

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

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

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

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

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

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

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

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

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

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

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

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

InterfaceBody:
{ {Объявление члена интерфейса} }
InterfaceMemberDeclaration:
Объявление константы
Объявление метода интерфейса
Объявление класса
Объявление интерфейса
;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Пример 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 присвоит значению YELLOW значение 3 вместо 8, ссылка на поле YELLOW внутри интерфейса LotsOfColors по-прежнему будет считаться неоднозначной.


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

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

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


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

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

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

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

Ошибка времени компиляции, если в инициализаторе поля интерфейса используется ключевое слово this (§15.8.3) или ключевое слово super (§15.11.2, §15.12), если это не находится в теле анонимного класса (§15.9.5).

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

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

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

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

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


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

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

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

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

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

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

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

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

Ошибка времени компиляции — использование имени параметра типа любой окружающей декларации в заголовке или теле метода static интерфейса.

Действие модификатора strictfp — сделать все float или double выражения в теле метода по умолчанию или static метода явно FP-строгими (§15.4).

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

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

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

Ошибка времени компиляции — если объявление метода abstract содержит ключевое слово strictfp.

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

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

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

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

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

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

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

Обратите внимание, что методы переопределяются на основе сигнатуры. Например, если интерфейс объявляет два 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.

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

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

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

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

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

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

  • Сигнатура m1 является подсигнатурой (§8.4.2) сигнатуры m2.

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

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

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

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

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

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

Ошибка компиляции возникает, если метод по умолчанию эквивалентен переопределению методу, не являющемуся 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-пункты не вызывают ошибок.)

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

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

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

В отличие от этого, долгое время поведение унаследованных конкретных методов в классах заключалось в том, что они переопределяли абстрактные методы, объявленные в интерфейсах (см. §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. Тело метода интерфейса

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

Метод static также имеет тело блока, которое предоставляет реализацию метода.

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

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

Ошибка компиляции, если тело метода static пытается обратиться к текущему объекту с использованием ключевого слова this или ключевого слова super.

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

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

9.5. Объявления типов членов

Интерфейсы могут содержать объявления типов членов (§8.5).

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

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

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

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

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

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

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

9.6. Типы аннотаций

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

AnnotationTypeDeclaration:
{МодификаторИнтерфейса} @ interface Идентификатор ТелоТипаАннотации

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

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

Идентификатор в объявлении типа аннотации задаёт имя типа аннотации.

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

Прямым суперинтерфейсом каждого типа аннотации является java.lang.annotation.Annotation.

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

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

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

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

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

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

9.6.1. Элементы типа аннотации

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

AnnotationTypeBody:
{ {ОбъявлениеЧленаТипаАннотации} }
AnnotationTypeMemberDeclaration:
ОбъявлениеЭлементаТипаАннотации
ОбъявлениеКонстанты
ОбъявлениеКласса
ОбъявлениеИнтерфейса
;
AnnotationTypeElementDeclaration:
{МодификаторЭлементаТипаАннотации} НеквалифицированныйТип Идентификатор ( ) [Размеры] [ЗначениеПоУмолчанию] ;
AnnotationTypeElementModifier:
(один из)
Аннотация public
abstract

В силу правила AnnotationTypeElementDeclaration, объявление метода в объявлении типа аннотации не может иметь формальные параметры, параметры типа или throws-клаузу. Следующее правило из §4.3 приводится здесь для удобства:

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

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

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

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

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

  • String

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

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

  • Тип аннотации

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

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

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

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

Если любой метод, объявленный в типе аннотации, имеет сигнатуру, эквивалентную по переопределению любой 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 type is not designed
 * to be used directly to annotate program elements, but to
 * define elements of other annotation types.
 */
@interface Name {
    String first();
    String last();
}

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

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

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

Элемент типа аннотации может иметь значение по умолчанию, указанное после списка параметров элемента (пустого) с ключевым словом default и Значением элемента (§9.7.1).

DefaultValue:
default ЗначениеЭлемента

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

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

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

Вот уточнение типа аннотации RequestForEnhancement из §9.6.1:

@interface RequestForEnhancementDefault {
    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. Повторяемые типы аннотаций

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

Тип аннотации TC является содержащим типом аннотации T, если все из перечисленного ниже верно:

  1. TC объявляет метод value(), возвращающий тип T[].

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

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

    • Если сохранение TC равно java.lang.annotation.RetentionPolicy.SOURCE, то сохранение T равно java.lang.annotation.RetentionPolicy.SOURCE.

    • Если сохранение TC равно java.lang.annotation.RetentionPolicy.CLASS, то сохранение T равно либо java.lang.annotation.RetentionPolicy.CLASS, либо java.lang.annotation.RetentionPolicy.SOURCE.

    • Если сохранение TC равно java.lang.annotation.RetentionPolicy.RUNTIME, то сохранение T равно java.lang.annotation.RetentionPolicy.SOURCE, java.lang.annotation.RetentionPolicy.CLASS, или java.lang.annotation.RetentionPolicy.RUNTIME.

  4. T применима к по крайней мере тем же видам программных элементов, что и TC (§9.6.4.1). Конкретно, если виды программных элементов, к которым применима T, обозначены набором m1, а виды программных элементов, к которым применима TC, обозначены набором 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. Если объявление T имеет (мета-)аннотацию, соответствующую java.lang.annotation.Documented, то объявление TC должно иметь (мета-)аннотацию, соответствующую java.lang.annotation.Documented.

    Обратите внимание, что допускается, что TC является @Documented, в то время как T не является @Documented.

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

    Обратите внимание, что допускается, что TC является @Inherited, в то время как T не является @Inherited.

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

Пример 9.6.3-1. Неправильный содержащий тип аннотации

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

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

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

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


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

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

Тип аннотации может быть содержащим типом аннотации не более одного типа аннотации.

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

Тип аннотации не может указывать сам себя как содержащий тип аннотации.

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

Тип аннотации TC может быть содержащим типом аннотации для некоторого типа аннотации T, одновременно имея свой собственный содержащий тип аннотации TC '. То есть, содержащий тип аннотации может сам быть повторяемым типом аннотации.

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

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

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

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

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

@Foo @Foo
@interface X {}

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

@Foo @Foo
interface X {}

Более широко, если 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. Повторяемый содержащий тип аннотации

Следующие объявления корректны:


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

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

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

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


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

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


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

В библиотеках платформы Java SE определены несколько типов аннотаций. Некоторые из этих предопределённых типов аннотаций имеют специальную семантику. Данная секция описывает эту семантику. Данная секция не предоставляет полное описание предопределённых аннотаций, содержащихся здесь; эту роль выполняют соответствующие спецификации API. Здесь указаны только те семантики, которые требуют специального поведения со стороны компилятора Java или реализации виртуальной машины Java.

9.6.4.1. @Target

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

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

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

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

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

  2. Объявления типов: class, interface, enum и объявления типов аннотаций (§8.1.1, §9.1.1, §8.5, §9.5, §8.9, §9.6)

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

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

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

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

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

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

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

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

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

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

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

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

  8. Объявления локальных переменных (включая переменные циклов в циклах for и переменные ресурсов в инструкциях try-с-ресурсами) (§14.4, §14.14.1, §14.14.2, §14.20.3)

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

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

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

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

Эти контексты — синтаксические места, где аннотации разрешались в Java SE 7.

9.6.4.2. @Retention

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

Если аннотация a соответствует типу T, и T имеет (мета-)аннотацию 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, если только m не аннотирует объявление локальной переменной.

    Аннотация на объявлении локальной переменной никогда не сохраняется в двоичном представлении.

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

Если у T нет (мета-)аннотации m, соответствующей java.lang.annotation.Retention, то компилятор Java должен обрабатывать T так, как будто у неё есть такая мета-аннотация m с элементом, значение которого 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, что может привести к некоторым очень скрытым ошибкам.

Если объявление метода аннотировано типом аннотации @Override, но метод не перекрывает или не реализует метод, объявленный в супертипе, или не является эквивалентным методу public в Object, возникает ошибка компиляции.

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

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

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

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

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

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

interface Quux { @Override Object clone(); }

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

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

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

9.6.4.5. @SuppressWarnings

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

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

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

Непроверяемые предупреждения идентифицируются строкой "unchecked".

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

9.6.4.6. @Deprecated

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

Компилятор Java должен выдавать предупреждение о устаревании, когда тип, метод, поле или конструктор, объявление которого аннотировано @Deprecated, используется (перекрывается, вызывается или ссылается по имени) в конструкции, которая явно или неявно объявлена, за исключением случаев:

  • Использование находится внутри сущности, которая сама аннотирована аннотацией @Deprecated; или

  • Использование находится внутри сущности, которая аннотирована для подавления предупреждения аннотацией @SuppressWarnings("deprecation"); или

  • Использование и объявление находятся в одном и том же внешнем классе.

Использование аннотации @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, аннотировано аннотацией @SafeVarargs.

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

9.6.4.8. @Repeatable

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

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

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:
@ ИмяТипа ( [СписокПарИменЗначений] )
ElementValuePairList:
ПараИменЗначения {, ПараИменЗначения}
ElementValuePair:
Идентификатор = ЗначениеЭлемента
ElementValue:
УсловноеВыражение
ИнициализаторМассиваЗначенийЭлементов
Аннотация
ElementValueArrayInitializer:
{ [СписокЗначенийЭлементов] [,] }
ElementValueList:
ЗначениеЭлемента {, ЗначениеЭлемента}

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

ИмяТипа задаёт тип аннотации, соответствующей аннотации. Аннотация считается «типа» этого типа.

Если ИмяТипа не указывает доступный тип аннотации (§6.6) в точке появления аннотации, это ошибка компиляции.

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

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

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

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

  • T является типом массива E[], и либо:

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

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

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

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

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

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

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

    • V не null.

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

Формально, говорить о ЗначенииЭлемента как о строго FP (§15.4) некорректно, так как оно может быть аннотацией или литералом класса. Тем не менее, можно говорить о ЗначенииЭлемента как о строго FP, когда это константное выражение, массив константных выражений или аннотация, значения элементов которой (рекурсивно) являются константными выражениями; в конце концов, каждое константное выражение является строго FP.

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

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

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

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

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

Например, допустимо аннотировать объявление типа аннотации S метааннотацией типа T, а само объявление T — метааннотацией типа S. В предварительно определённых типах аннотаций есть несколько таких цикличности.

Пример 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()

Разрешено использовать аннотации-метки для типов аннотаций с элементами, при условии, что все элементы имеют значения по умолчанию (§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;

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

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

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

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

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

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

  • Объявления локальных переменных (включая переменные цикла в for операторах и переменные ресурсов в try операторах с ресурсами)

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

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

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

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

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

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

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

Существуют два особых случая, касающиеся объявлений методов/конструкторов:

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

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

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

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

  • объявления класса, интерфейса или перечисления, но T неприменим к объявлениям типов или контекстам типов; или объявления типа аннотации, но T неприменим к объявлениям типов аннотаций или контекстам типов.

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

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

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

  • объявления поля (включая константу перечисления), но T неприменим к объявлениям полей или контекстам типов.

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

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

  • объявления локальной переменной (включая переменную цикла оператора for или переменную ресурса оператора try-с-ресурсами), но T неприменим к объявлениям локальных переменных или контекстам типов.

Обратите внимание, что в большинстве пунктов выше упоминается "... или контексты типов", так как даже если аннотация не применяется к объявлению, она всё равно может применяться к типу объявленной сущности.

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

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

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

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

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


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

class Test {
  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 
}

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


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

class Test {
  class E {
    class F {
      static class G {}
    }
  }

  @Foo E.F.G x;
}

Предположим на мгновение, что вложение допустимо. В типе поля x, E и F логически будут именами, квалифицирующими G, так как E.F.this будет недопустимо в теле G. Тогда @Foo не должно быть допустимо рядом с E. Однако технически @Foo будет допустимо рядом с E, потому что следующее самое глубокое выражение F обозначает внутренний класс; но это не имеет значения, так как вложение классов недопустимо в первую очередь.

Ошибка компиляции, если аннотация типа T применяется к внешнему уровню типа в контексте типа, и T неприменима в контекстах типов или контексте объявления (при его наличии), занимающем то же синтаксическое место.

Ошибка компиляции, если аннотация типа T применяется к части типа (то есть не к внешнему уровню) в контексте типа, и T неприменима в контекстах типов.

Ошибка компиляции, если аннотация типа T применяется к типу (или любой его части) в контексте типа, и T применима в контекстах типов, и аннотация недопустима.

Например, предположим тип аннотации 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. Несколько аннотаций одного типа

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

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

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

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

Ошибка компиляции, если в контексте объявления или контексте типа есть несколько аннотаций повторяемого типа аннотации T и какие-либо аннотации содержащего типа аннотации T.

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


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

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

Ошибка компиляции, если в контексте объявления или контексте типа присутствует одна аннотация повторяемого типа аннотации T и несколько аннотаций содержащего типа аннотации T.

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


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

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


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

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

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

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

Для интерфейса I, пусть M будет множеством abstract методов, которые являются членами I, не имеющих одинаковой подписи с любым public методом-членом класса Object. Тогда 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 метода, который в противном случае был бы неявно объявлен и будет автоматически реализован каждым классом, который implements интерфейс.

Обратите внимание, что если не-public методы Object, такие как clone(), объявлены в интерфейсе, они не автоматически реализуются каждым классом, который implements интерфейс. Реализация, унаследованная от Object, является protected, в то время как метод интерфейса обязательно является public. Единственный способ реализовать такой интерфейс — это для класса переопределить метод non-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 — множество абстрактных методов, определённых для I. Тип функции I состоит из следующего:

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

    Пусть m — метод в M, обладающий:

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

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

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

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

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

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

  • throws клаузула:

    throws клаузула типа функции происходит от throws клаузул методов в M. Если тип функции является параметризованным, эти клаузулы сначала адаптируются к параметрам типа типа функции (§8.4.4). Если тип функции не является параметризованным, но хотя бы один метод в M является параметризованным, эти клаузулы сначала удаляются. Затем, throws клаузула типа функции включает каждый тип E, который удовлетворяет следующим ограничениям:

    • E указан в одной из throws клаузул.

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

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

Целью, определяющей типы выбрасываемых исключений типа функции, является поддержка инварианта, что метод с полученной throws клаузой может переопределить каждый абстрактный метод функционального интерфейса. По §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 оговорка "наиболее специфичного метода" также определяются недетерминированно, когда существует несколько абстрактных методов (§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. Обобщённые типы функций

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

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