Глава 9. Интерфейсы
Оглавление
- 9.1. Объявления интерфейсов
- 9.2. Члены интерфейса
- 9.3. Объявления полей (констант)
- 9.4. Объявления методов
- 9.5. Объявления типов членов
- 9.6. Типы аннотаций
- 9.7. Аннотации
- 9.8. Функциональные интерфейсы
- 9.9. Типы функций
Объявление интерфейса вводит новый тип ссылки, члены которого представляют собой классы, интерфейсы, константы и методы. Этот тип не имеет переменных экземпляров и обычно объявляет один или несколько abstract методов; в противном случае не связанные классы могут реализовать интерфейс, предоставив реализации его abstract методов. Интерфейсы не могут быть напрямую инстанцированы.
Вложенный интерфейс — это любой интерфейс, объявление которого происходит внутри тела другого класса или интерфейса.
Интерфейс верхнего уровня — это интерфейс, который не является вложенным интерфейсом.
Мы различаем два вида интерфейсов — обычные интерфейсы и типы аннотаций.
В этой главе рассматриваются общие семантические аспекты всех интерфейсов — обычных интерфейсов, как верхнего уровня (§7.6), так и вложенных (§8.5, §9.5), и типов аннотаций (§9.6). Подробности, специфичные для определенных видов интерфейсов, обсуждаются в разделах, посвященных этим конструкциям.
Программы могут использовать интерфейсы, чтобы избежать необходимости для связанных классов в общем abstract суперклассе или для добавления методов к Object.
Интерфейс может быть объявлен как прямое расширение одного или нескольких других интерфейсов, что означает, что он наследует все типы членов, методы экземпляров и константы интерфейсов, которые он расширяет, за исключением любых членов, которые он может переопределить или скрыть.
Класс может быть объявлен для прямой реализации одного или нескольких интерфейсов, что означает, что любой экземпляр класса реализует все abstract методы, указанные в интерфейсе или интерфейсах. Класс обязательно реализует все интерфейсы, которые реализуют его непосредственные суперклассы и непосредственные суперинтерфейсы. Это (множественное) наследование интерфейсов позволяет объектам поддерживать (множественные) общие поведения без общего суперкласса.
Переменная, тип которой объявлен как тип интерфейса, может иметь в качестве своего значения ссылку на любой экземпляр класса, реализующего указанный интерфейс. Недостаточно, чтобы класс случайным образом реализовывал все abstract методы интерфейса; класс или один из его суперклассов должен быть фактически объявлен для реализации интерфейса, иначе класс не считается реализующим интерфейс.
Объявление интерфейса задаёт новый именованный тип ссылки. Существуют два вида объявлений интерфейсов — обычные объявления интерфейсов и объявления типов аннотаций (§9.6).
ИдентификаторТипа в объявлении интерфейса указывает имя интерфейса.
Если интерфейс имеет то же простое имя, что и любой из его окружающих классов или интерфейсов, то это ошибка времени компиляции.
Область действия и перекрытие объявления интерфейса указаны в §6.3 и §6.4.
Объявление интерфейса может содержать модификаторы интерфейсов.
Правила модификаторов аннотаций для объявления интерфейса описаны в §9.7.4 и §9.7.5.
Модификатор доступа public (§6.6) относится ко всем видам объявлений интерфейсов.
Модификаторы доступа protected и private относятся только к вложенным интерфейсам, объявления которых непосредственно заключены в объявлении класса (§8.5.1).
Модификатор static относится только к вложенным интерфейсам (§8.5.1, §9.5), а не к интерфейсам верхнего уровня (§7.6).
Ошибка времени компиляции возникает, если одно и то же ключевое слово используется более одного раза в качестве модификатора для объявления интерфейса или если в объявлении интерфейса используется более одного из модификаторов доступа public, protected и private (§6.6).
Если в объявлении интерфейса присутствует два или более (различных) модификатора интерфейса, то принято, хотя и не обязательно, что они располагаются в порядке, согласованном с представленным выше в продукции для МодификатораИнтерфейса.
Каждый интерфейс неявным образом является abstract.
Этот модификатор устарел и не должен использоваться в новых программах.
Эффект модификатора strictfp заключается в том, что все float или double выражения внутри объявления интерфейса становятся явно FP-строгими (§15.4).
Это подразумевает, что все методы, объявленные в интерфейсе, и все вложенные типы, объявленные в интерфейсе, неявно являются strictfp.
Интерфейс является параметризованным, если он объявляет одну или несколько переменных типов (§4.4).
Эти переменные типа известны как параметры типа интерфейса. Раздел параметров типа следует за именем интерфейса и ограничен угловыми скобками.
Следующие правила из §8.1.2 и §4.4 приведены здесь для удобства:
Правила модификаторов аннотаций для объявления параметра типа описаны в §9.7.4 и §9.7.5.
В разделе параметров типа интерфейса переменная типа T непосредственно зависит от переменной типа S, если S является ограничением T, тогда как T зависит от S, если либо T непосредственно зависит от S, либо T непосредственно зависит от переменной типа U, которая зависит от S (используя это определение рекурсивно). Ошибка времени компиляции возникает, если переменная типа в разделе параметров типа интерфейса зависит от самой себя.
Область действия и перекрытие параметра типа интерфейса описаны в §6.3.
Ошибка времени компиляции возникает при ссылке на параметр типа параметризованного интерфейса I где-либо в объявлении static члена интерфейса I (§9.3, §9.4, §9.5).
Объявление параметризованного интерфейса определяет набор параметризованных типов (§4.5), по одному для каждой возможной параметризации раздела параметров типа аргументами типа. Все эти параметризованные типы совместно используют один и тот же интерфейс во время выполнения.
Если указан пункт extends, то объявляемый интерфейс расширяет каждый из других перечисленных интерфейсов и, следовательно, наследует типы членов, методы экземпляров и константы каждого из других перечисленных интерфейсов.
Эти другие перечисленные интерфейсы являются прямыми суперинтерфейсами объявляемого интерфейса.
Любой класс, который implements объявленный интерфейс, также считается реализующим все интерфейсы, которые этот интерфейс extends.
extends Список типов интерфейсов Для удобства приводится следующее производство из §8.1.5:
Каждый Тип интерфейса в пункте extends объявления интерфейса должен указывать на доступный тип интерфейса (§6.6), иначе происходит ошибка времени компиляции.
Если у Типа интерфейса есть аргументы типа, он должен обозначать правильно сформированный параметризованный тип (§4.5), и ни один из аргументов типа не может быть аргументом типа с подстановкой, иначе происходит ошибка времени компиляции.
Для объявления интерфейса (возможно, обобщенного) I<F1,...,Fn> (n ≥ 0), прямыми суперинтерфейсами типа интерфейса I<F1,...,Fn> являются типы, указанные в пункте extends объявления I, если такой пункт присутствует.
Для обобщенного объявления интерфейса 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.1.3).
-
Если у интерфейса нет непосредственных суперинтерфейсов, то интерфейс неявно объявляет метод-член
publicabstractс сигнатурой s, возвращаемым типом r иthrowsоговоркой t, соответствующий каждому методу-членуpublicэкземпляра с сигнатурой s, возвращаемым типом r иthrowsоговоркой t, объявленному вObject(§4.3.2), если методabstractс той же сигнатурой, тем же возвращаемым типом и совместимойthrowsоговоркой явно не объявлен в интерфейсе.Ошибка компиляции, если интерфейс явно объявляет такой метод
mв случае, когдаmобъявлен какfinalвObject.Ошибка компиляции, если интерфейс явно объявляет метод с сигнатурой, которая является эквивалентной для переопределения (§8.4.2) для метода
publicклассаObject, но с другим возвращаемым типом или несовместимойthrowsоговоркой, или если он неabstract.
Интерфейс наследует от интерфейсов, которые он расширяет, все члены этих интерфейсов, за исключением (i) полей, классов и интерфейсов, которые он скрывает, (ii) abstract методов и методов по умолчанию, которые он переопределяет (§9.4.1), (iii) private методов и (iv) static методов.
Поля, методы и типы членов типа интерфейса могут иметь одинаковое имя, поскольку они используются в разных контекстах и различаются различными процедурами поиска (§6.5). Тем не менее, это не рекомендуется с точки зрения стиля.
См. §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 присвоит значение 3 полю YELLOW вместо значения 8, ссылка на поле YELLOW внутри интерфейса LotsOfColors по-прежнему будет считаться неоднозначной.
Пример 9.3-2. Поля, унаследованные несколько раз
Если одно поле унаследовано несколько раз от одного и того же интерфейса, потому что, например, этот интерфейс и один из его прямых суперинтерфейсов наследуют интерфейс, который объявляет поле, то получается только одно поле. Эта ситуация сама по себе не является ошибкой компиляции.
В предыдущем примере поля RED, GREEN и BLUE наследуются интерфейсом LotsOfColors более чем одним способом, через интерфейс RainbowColors и также через интерфейс PrintColors, но ссылка на поле RED в интерфейсе LotsOfColors не считается неоднозначной, поскольку участвует только одно фактическое объявление поля RED.
Каждый идентификатор в объявлении поля интерфейса должен иметь инициализатор, в противном случае произойдёт ошибка компиляции.
Инициализатор не обязательно должен быть константным выражением (§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.
Следующие правила из §8.4, §8.4.5 и §8.4.7 представлены здесь для удобства:
Правила для модификаторов аннотаций в объявлении метода интерфейса указаны в §9.7.4 и §9.7.5.
Метод в теле интерфейса может быть объявлен public или private (§6.6). Если модификатор доступа не указан, метод неявно public. Допускается, но не рекомендуется с точки зрения стиля, избыточно указывать модификатор public для объявления метода в интерфейсе.
Метод по умолчанию — это метод экземпляра, объявленный в интерфейсе с модификатором default. Его тело всегда представлено блоком, который предоставляет реализацию по умолчанию для любого класса, реализующего интерфейс без переопределения метода. Методы по умолчанию отличаются от конкретных методов (§8.4.3.1), которые объявляются в классах, и от методов private интерфейса, которые не наследуются и не переопределяются.
Интерфейс может объявлять static методы, которые вызываются без ссылки на конкретный объект. static методы интерфейса отличаются от методов по умолчанию, которые являются методами экземпляра.
Ошибка компиляции, если имя параметра типа любого окружающего объявления используется в заголовке или теле static метода интерфейса.
Эффект модификатора strictfp заключается в том, что все float или double выражения в теле метода по умолчанию или static метода будут явно FP-строгими (§15.4).
Метод интерфейса без модификатора private, default или static неявно abstract. Его тело представлено точкой с запятой, а не блоком. Допускается, но не рекомендуется с точки зрения стиля, избыточно указывать модификатор abstract для такого объявления метода.
Обратите внимание, что метод интерфейса не может быть объявлен с protected или пакетным доступом, или с модификаторами final, synchronized или native.
Ошибка компиляции, если одно и то же ключевое слово используется более одного раза в качестве модификатора для объявления метода интерфейса или если объявление метода интерфейса имеет более одного из модификаторов доступа public и private (§6.6).
Ошибка компиляции, если объявление метода интерфейса имеет более одного из ключевых слов abstract, default или static.
Ошибка компиляции, если объявление метода интерфейса, содержащее ключевое слово private, также содержит ключевые слова abstract или default. Допускается, чтобы объявление метода интерфейса содержало как private, так и static.
Ошибка компиляции, если объявление метода интерфейса, содержащее ключевое слово abstract, также содержит ключевое слово strictfp.
Ошибка компиляции, если тело интерфейса явно или неявно объявляет два метода с эквивалентными сигнатурами переопределения (§8.4.2). Однако интерфейс может унаследовать несколько abstract методов с такими сигнатурами (§9.4.1).
Метод, объявленный в интерфейсе, может быть обобщённым. Правила для параметров типа обобщённого метода в интерфейсе такие же, как для обобщённого метода в классе (§8.4.4).
Интерфейс 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.
Интерфейс не наследует private или static методы от своих суперинтерфейсов.
Если интерфейс I объявляет private или 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 методов в суперклассах/суперинтерфейсах, тогда как правило выше рассматривает только public методы экземпляра в суперинтерфейсах.
В том же духе, метод private в интерфейсе не может переопределить метод экземпляра — будь то public или private — в суперинтерфейсе. Это аналогично правилам в §8.4.8.1 и §8.4.8.3, где метод private в классе не может переопределить какой-либо метод экземпляра в суперклассе или суперинтерфейсе, потому что §8.4.8.1 требует, чтобы переопределяемый метод был не-private, а §8.4.8.3 требует, чтобы переопределяющий метод обеспечивал как минимум такой же доступ, как и переопределяемый метод. Подводя итог, только public методы в интерфейсах могут быть переопределены, и только public методами в подинтерфейсах или в реализующих классах.
Метод экземпляра mI, объявленный или унаследованный интерфейсом I, переопределяет из I другой метод экземпляра mJ, объявленный в интерфейсе J, если выполняются все следующие условия:
-
I является подинтерфейсом J.
-
I не наследует
mJ. -
Сигнатура
mIявляется подсигнатурой (§8.4.2) сигнатурыmJ. -
mJявляетсяpublic.
Наличие или отсутствие модификатора strictfp совершенно не влияет на правила переопределения методов. Например, метод, не являющийся FP-строгим, может переопределить FP-строгий метод, и FP-строгий метод может переопределить метод, не являющийся FP-строгим.
Переопределённый метод по умолчанию можно получить, используя выражение вызова метода (§15.12), которое содержит ключевое слово super, квалифицированное именем суперинтерфейса.
Отношение между типом возвращаемого значения метода интерфейса и типами возвращаемых значений любых переопределённых методов интерфейса указано в §8.4.8.3.
Отношение между throws частью метода интерфейса и throws частями любых переопределённых методов интерфейса указано в §8.4.8.3.
Отношение между сигнатурой метода интерфейса и сигнатурами любых переопределённых методов интерфейса указано в §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 в классах затем могли бы делегировать этому методу.
Интерфейс может унаследовать несколько методов с эквивалентными подписями для переопределения (§8.4.2).
Если интерфейс I наследует метод по умолчанию, чья подпись эквивалентна подписи другого метода, унаследованного интерфейсом I, то возникает ошибка компиляции. (Это справедливо, независимо от того, является ли другой метод abstract или default.)
В противном случае, все унаследованные методы являются abstract, и интерфейс считается наследующим все методы.
Один из унаследованных методов должен быть совместим по типу возвращаемого значения с любым другим унаследованным методом, в противном случае возникает ошибка компиляции. (throws-условия в этом случае не вызывают ошибок.)
Может быть несколько путей, по которым одно и то же объявление метода наследуется от интерфейса. Этот факт не вызывает затруднений и никогда сам по себе не приводит к ошибке компиляции.
Естественно, когда два разных метода по умолчанию с совпадающими подписями наследуются под-интерфейсом, возникает конфликт поведения. Мы активно обнаруживаем этот конфликт и уведомляем программиста об ошибке, вместо того, чтобы ждать возникновения проблемы во время компиляции конкретного класса. Ошибку можно избежать, объявив новый метод, который переопределяет и тем самым предотвращает наследование всех конфликтующих методов.
Аналогично, когда абстрактный метод и метод по умолчанию с совпадающими подписями наследуются под-интерфейсом, мы генерируем ошибку. В этом случае было бы возможно отдать приоритет одному из них — возможно, мы бы предположили, что метод по умолчанию предоставляет разумную реализацию для абстрактного метода. Но это рискованно, так как помимо совпадающего имени и подписи, у нас нет оснований полагать, что метод по умолчанию ведет себя согласованно с контрактом абстрактного метода — метод по умолчанию мог вообще не существовать, когда под-интерфейс был первоначально разработан. В этой ситуации безопаснее попросить пользователя подтвердить, что реализация по умолчанию подходит (путем объявления переопределения).
В отличие от этого, давно используемое поведение для наследования конкретных методов в классах заключается в том, что они переопределяют абстрактные методы, объявленные в интерфейсах (см. §8.4.8). Тот же аргумент о потенциальном нарушении контракта здесь применим, но в этом случае существует некоторая внутренняя несбалансированность между классами и интерфейсами. Мы предпочитаем, чтобы сохранить независимость иерархий классов, минимизировать столкновения класс-интерфейс, просто отдавая приоритет конкретным методам.
Если у двух методов интерфейса (независимо от того, объявлены ли они в одном интерфейсе, или оба унаследованы интерфейсом, или один объявлен, а другой унаследован) одинаковое имя, но разные подписи, которые не являются эквивалентными для переопределения (§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, должен предоставить реализации всех трех подписей метода.
Метод по умолчанию имеет тело блока. Этот блок кода предоставляет реализацию метода в случае, если класс реализует интерфейс, но не предоставляет собственную реализацию метода.
private или static метод также имеет тело блока, которое предоставляет реализацию метода.
Ошибка компиляции, если объявление метода интерфейса abstract (явным или неявным образом) и имеет блок для своего тела.
Ошибка компиляции, если объявление метода интерфейса default, private или static и имеет точку с запятой для своего тела.
Ошибка компиляции, если тело static метода пытается получить доступ к текущему объекту с помощью ключевого слова this или ключевого слова super.
Правила для return операторов в теле метода указаны в §14.17.
Если метод объявлен с типом возвращаемого значения (§8.4.5), то возникает ошибка компиляции, если тело метода может завершиться нормально (§14.1).
Интерфейсы могут содержать объявления типов членов (§8.5).
Каждое объявление типа члена в теле интерфейса неявным образом public и static. Разрешено избыточно указывать один или оба из этих модификаторов.
Ошибка компиляции, если объявление типа члена в интерфейсе имеет модификатор protected или private.
Ошибка компиляции, если одно и то же ключевое слово появляется более одного раза в качестве модификатора для объявления типа члена в интерфейсе.
Если интерфейс объявляет тип члена с определенным именем, то объявление этого типа считается скрывающим все и любые доступные объявления типов членов с тем же именем в суперинтерфейсах интерфейса.
Интерфейс наследует от своих непосредственных суперинтерфейсов все типы членов, которые не являются private, типы членов суперинтерфейсов, которые доступны коду в интерфейсе и не скрыты объявлением в интерфейсе.
Возможно, что интерфейс унаследует более одного типа члена с одинаковым именем. Такая ситуация сама по себе не приводит к ошибке компиляции. Однако любая попытка внутри тела интерфейса обратиться к любому такому типу члена по его простому имени приведет к ошибке компиляции, потому что ссылка неоднозначна.
Может быть несколько путей, по которым одно и то же объявление типа члена наследуется от интерфейса. В такой ситуации тип члена считается унаследованным только один раз, и к нему можно обращаться по его простому имени без неоднозначности.
Объявление типа аннотации задаёт новый тип аннотации, специальный вид интерфейса. Чтобы отличить объявление типа аннотации от обычного объявления интерфейса, ключевое слово 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). Без этого правила мы не могли бы гарантировать, что элементы имели типы, представимые в аннотациях, или что для них были бы доступны методы-аксессоры.
Если здесь не указано иное, все правила, применимые к обычным объявлениям интерфейсов, применяются и к объявлениям типов аннотаций.
Например, типы аннотаций используют ту же область имён, что и обычные классы и интерфейсы; объявления типов аннотаций допустимы там, где допустимы объявления интерфейсов, и имеют такой же объём и доступность.
Тело объявления типа аннотации может содержать объявления методов, каждый из которых определяет элемент типа аннотации. Тип аннотации не имеет элементов, кроме тех, которые определены явно объявленными им методами.
В силу производства AnnotationTypeElementDeclaration, в объявлении метода типа аннотации не могут быть формальные параметры, параметры типа или throws. Следующее производство из §4.3 показано здесь для удобства:
Благодаря производству AnnotationTypeElementModifier, объявление метода в объявлении типа аннотации не может быть default или static. Таким образом, тип аннотации не может объявлять такое же разнообразие методов, как обычный тип интерфейса. Обратите внимание, что тип аннотации всё ещё может унаследовать метод по умолчанию от своего неявного суперинтерфейса, java.lang.annotation.Annotation, хотя такого метода по умолчанию не существует в Java SE 11.
По соглашению, единственными AnnotationTypeElementModifierами, которые должны присутствовать на элементе типа аннотации, являются аннотации.
Тип возвращаемого значения метода, объявленного в типе аннотации, должен быть одним из следующих, иначе произойдёт ошибка времени компиляции:
Это правило исключает элементы со вложенными типами массивов, такие как:
@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();
}
Элемент типа аннотации может иметь значение по умолчанию, указанное после списка параметров элемента (пустого) с ключевым словом default и Значением элемента (§9.7.1).
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]";
}
Тип аннотации T является повторяющимся, если его объявление (мета-)аннотировано аннотацией @Repeatable (§9.6.4.8), элемент value которого указывает содержащий тип аннотации T.
Тип аннотации TC является содержащим типом аннотации T, если все перечисленные ниже условия являются истинными:
-
TC объявляет метод
value(), возвращающий тип T[]. -
Любые другие методы, объявленные типом TC, кроме
value(), должны иметь значение по умолчанию. -
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.
-
-
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—m2, то хотя бы один из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.
Этот пункт реализует политику, согласно которой тип аннотации может быть повторяющимся только на некоторых видах элементов программы, к которым он применим.
-
-
Если объявление T имеет (мета-)аннотацию, соответствующую
java.lang.annotation.Documented, то объявление TC должно иметь (мета-)аннотацию, соответствующуюjava.lang.annotation.Documented.Обратите внимание, что TC может быть
@Documented, а T — нет@Documented. -
Если объявление T имеет (мета-)аннотацию, соответствующую
java.lang.annotation.Inherited, то объявление TC должно иметь (мета-)аннотацию, соответствующуюjava.lang.annotation.Inherited.Обратите внимание, что TC может быть
@Inherited, а T — нет@Inherited.
Ошибка компиляции, если тип аннотации T (мета-)аннотирован аннотацией @Repeatable, элемент value которой указывает тип, который не является содержащим типом аннотации T.
Пример 9.6.3-1. Некорректный содержащий тип аннотации
Рассмотрим следующие объявления:
import java.lang.annotation.Repeatable;
@Repeatable(FooContainer.class)
@interface Foo {}
@interface FooContainer { Object[] value(); }
Компиляция объявления Foo приводит к ошибке компиляции, потому что Foo пытается указать FooContainer как содержащий тип аннотации, но FooContainer на самом деле не является содержащим типом аннотации Foo. (Тип возвращаемого значения FooContainer.value() не Foo[].)
Аннотация @Repeatable не может быть повторена, поэтому только один содержащий тип аннотации может быть указан повторяющимся типом аннотации.
Разрешение указания более одного содержащего типа аннотации привело бы к нежелательному выбору на этапе компиляции, когда несколько аннотаций одного и того же типа логически заменяются аннотацией-контейнером (§9.7.5).
Тип аннотации может быть содержащим типом аннотации для не более одного другого типа аннотации.
Это вытекает из требования, что если объявление типа аннотации 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. Например, при следующих объявлениях повторяемых и содержащих типов аннотаций:
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
@Target(ElementType.TYPE)
@Repeatable(FooContainer.class)
@interface Foo {}
@Target(ElementType.ANNOTATION_TYPE)
@interface FooContainer {
Foo[] value();
}
@Foo может появляться на любом объявлении типа, тогда как @FooContainer может появляться только на объявлениях типов аннотаций. Поэтому следующее объявление типа аннотации является допустимым:
@Foo @Foo
@interface Anno {}
в то время как следующее объявление интерфейса является недопустимым:
@Foo @Foo
interface Intf {}
Более широко, если Foo — это повторяемый тип аннотации, а FooContainer — это его содержащий тип аннотации, то:
-
Если у
Fooнет мета-аннотации@Target, а уFooContainerнет мета-аннотации@Target, то@Fooможно повторять на любом элементе программы, поддерживающем аннотации. -
Если у
Fooнет мета-аннотации@Target, но уFooContainerесть мета-аннотация@Target, то@Fooможно повторять только на элементах программы, где может появляться@FooContainer. -
Если у
Fooесть мета-аннотация@Target, то по мнению разработчиков языка программирования Java,FooContainerдолжно быть объявлено с учетом применимостиFoo. Конкретно, виды элементов программы, где может появлятьсяFooContainer, должны логически совпадать или быть подмножеством видов элементов, где может появлятьсяFoo.Например, если
Fooприменима к объявлениям полей и методов, тоFooContainerможет законно служить содержащим типом аннотации дляFoo, еслиFooContainerприменима только к объявлениям полей (что предотвращает повторение@Fooна объявлениях методов). Но еслиFooContainerприменима только к объявлениям формальных параметров, тоFooContainerбыл плохим выбором содержащего типа аннотации дляFoo, потому что@FooContainerне может быть неявно объявлено на некоторых элементах программы, где@Fooповторяется.Аналогично, если
Fooприменима к объявлениям полей и методов, тоFooContainerне может законно служить содержащим типом аннотации дляFoo, еслиFooContainerприменима к объявлениям полей и параметров. Хотя было бы возможно взять пересечение элементов программы и сделатьFooповторяемой только на объявлениях полей, наличие дополнительных элементов программы дляFooContainerуказывает на то, чтоFooContainerне был спроектирован как содержащий тип аннотации дляFoo. Поэтому было бы опасно дляFooполагаться на него.
Пример 9.6.3-3. Повторяемый содержащий тип аннотации
Следующие объявления являются допустимыми:
import java.lang.annotation.Repeatable;
// Foo: Repeatable annotation 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 Test {}
Тип аннотации, который является одновременно повторяемым и содержащим, подчиняется правилам смешивания аннотаций повторяемого типа аннотации с аннотациями содержащего типа аннотации (§9.7.5). Например, невозможно написать несколько аннотаций @Foo рядом с несколькими аннотациями @FooContainer, и невозможно написать несколько аннотаций @FooContainer рядом с несколькими аннотациями @FooContainerContainer. Однако, если тип FooContainerContainer сам по себе является повторяемым, то можно написать несколько аннотаций @Foo рядом с несколькими аннотациями @FooContainerContainer.
В библиотеках платформы Java SE определены несколько типов аннотаций. Некоторые из этих предопределённых типов аннотаций обладают специальной семантикой. Эта семантика описана в данном разделе. Данный раздел не предоставляет полное описание предопределённых аннотаций, содержащихся здесь; эта задача возложена на соответствующие спецификации API. Здесь описывается только та семантика, которая требует специального поведения со стороны компилятора Java или реализации виртуальной машины Java.
Аннотация типа java.lang.annotation.Target используется на объявлении типа аннотации T для указания контекстов, в которых T применима. java.lang.annotation.Target содержит единственный элемент value типа java.lang.annotation.ElementType[] для указания контекстов.
Типы аннотаций могут быть применимы в контекстах объявления, где аннотации применяются к объявлениям, или в контекстах типов, где аннотации применяются к типам, используемым в объявлениях и выражениях.
Существует девять контекстов объявления, каждый из которых соответствует константе перечисления java.lang.annotation.ElementType:
-
Объявления модулей (§7.7)
Соответствует
java.lang.annotation.ElementType.MODULE -
Объявления пакетов (§7.4.1)
Соответствует
java.lang.annotation.ElementType.PACKAGE -
Объявления типов: 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 -
Объявления методов (включая элементы типов аннотаций) (§8.4.3, §9.4, §9.6.1)
Соответствует
java.lang.annotation.ElementType.METHOD -
Объявления конструкторов (§8.8.3)
Соответствует
java.lang.annotation.ElementType.CONSTRUCTOR -
Объявления параметров типа для дженериков (классы, интерфейсы, методы и конструкторы) (§8.1.2, §9.1.2, §8.4.4, §8.8.4)
Соответствует
java.lang.annotation.ElementType.TYPE_PARAMETER -
Объявления полей (включая константы перечислений) (§8.3.1, §9.3, §8.9.1)
Соответствует
java.lang.annotation.ElementType.FIELD -
Объявления формальных и исключительных параметров (§8.4.1, §9.4, §14.20)
Соответствует
java.lang.annotation.ElementType.PARAMETER -
Объявления локальных переменных (включая переменные циклов в операторах
forи переменные ресурсов в операторахtry-with-resources) (§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.
Аннотации могут присутствовать только в исходном коде или в бинарной форме класса или интерфейса. Аннотация, присутствующая в бинарной форме, может или не может быть доступна во время выполнения через библиотеки рефлексии платформы 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, за исключением случаев, когдаaаннотирует объявление локальной переменной илиaаннотирует объявление формального параметра лямбда-выражения.Аннотация на объявлении локальной переменной или формального параметра лямбда-выражения никогда не сохраняется в бинарном представлении. В противоположность этому, аннотация на типе локальной переменной или формального параметра лямбда-выражения сохраняется в бинарном представлении, если тип аннотации определяет подходящую политику сохранения.
Обратите внимание, что не запрещается мета-аннотировать тип аннотации
@Target(java.lang.annotation.ElementType.LOCAL_VARIABLE)и@Retention(java.lang.annotation.RetentionPolicy.CLASS)или@Retention(java.lang.annotation.RetentionPolicy.RUNTIME).Если
mимеет элемент со значениемjava.lang.annotation.RetentionPolicy.RUNTIME, то библиотеки рефлексии платформы Java SE должны сделатьaдоступными во время выполнения.
Если у T нет (мета-)аннотации m, соответствующей java.lang.annotation.Retention, то компилятор Java должен обработать T так, как будто у неё есть такая мета-аннотация m с элементом, значением которого является java.lang.annotation.RetentionPolicy.CLASS.
Программисты иногда перегружают объявление метода, когда намереваются переопределить его, что приводит к скрытым проблемам. Тип аннотации Override поддерживает раннее обнаружение таких проблем.
Классический пример связан с методом equals. Программисты пишут следующее в классе Foo:
public boolean equals(Foo that) { ... }
когда они хотят написать:
public boolean equals(Object that) { ... }
Это совершенно законно, но класс Foo наследует реализацию equals от класса Object, что может вызвать некоторые скрытые ошибки.
Если объявление метода в типе T аннотировано @Override, но метод не переопределяет метод из супертипа T, объявленного в супертипе T (§8.4.8.1, §9.4.1.1), или не эквивалентно переопределяемому методу public класса Object (§4.3.2, §8.4.2), то возникает ошибка времени компиляции.
Это поведение отличается от Java SE 5.0, где @Override вызывала ошибку компиляции только в случае применения к методу, реализующему метод из суперинтерфейса, который также не присутствовал в суперклассе.
Положение о переопределении метода public мотивировано использованием @Override в интерфейсе. Рассмотрим следующие объявления типов:
class Foo { @Override public int hashCode() {..} }
interface Bar { @Override int hashCode(); }
Использование @Override в объявлении класса является законным по первому условию, поскольку Foo.hashCode переопределяет метод Object.hashCode из Foo.
Для объявления интерфейса обратите внимание на то, что, хотя у интерфейса нет Object в качестве супертипа, у интерфейса есть public члены abstract, соответствующие public членам Object (§9.2). Если интерфейс выбирает явно объявить их (то есть объявить члены, которые эквивалентны переопределяемым методам public из Object), то интерфейс считается переопределяющим их, и использование @Override разрешается.
Однако, рассмотрим интерфейс, который пытается использовать @Override для метода clone: (finalize также может быть использовано в этом примере)
interface Quux { @Override Object clone(); }
Поскольку Object.clone не является public, в Quux нет члена с именем clone. Поэтому явное объявление clone в Quux не считается "реализующим" какой-либо другой метод, и использование @Override является ошибочным. (Факт, что Quux.clone является public, не имеет значения.)
В отличие от этого, объявление класса, которое объявляет clone, просто переопределяет Object.clone, поэтому может использовать @Override:
class Beep { @Override protected Object clone() {..} }
Компиляторы Java всё чаще способны выдавать полезные предупреждения в стиле "lint". Для поощрения использования таких предупреждений должна быть возможность отключения предупреждения в части программы, когда программист знает, что предупреждение неуместно.
Тип аннотации SuppressWarnings поддерживает управление программистом предупреждениями, которые в противном случае выдает компилятор Java. Он определяет один элемент, который является массивом String.
Если объявление аннотировано @SuppressWarnings(value
= {S1, ..., Sk}), то компилятор Java должен подавлять (то есть не сообщать) любое предупреждение, указанное одним из S1 ... Sk, если это предупреждение было бы сгенерировано в результате аннотированного объявления или любой его части.
Три типа предупреждений, определённые языком программирования Java, задаются следующими строками:
Для других типов предупреждений поставщики компиляторов должны документировать поддерживаемые ими строки для @SuppressWarnings. Поставщикам рекомендуется сотрудничать, чтобы гарантировать, что одни и те же имена будут работать в разных компиляторах.
Программистам иногда не рекомендуется использовать определённые элементы программы (модули, типы, поля, методы и конструкторы), поскольку они опасны или существует лучшая альтернатива. Тип аннотации Deprecated позволяет компилятору предупреждать об использовании этих элементов программы.
Элемент программы устаревшего типа — это модуль, тип, поле, метод или конструктор, объявление которого аннотировано с помощью @Deprecated. Способ, которым элемент программы считается устаревшим, зависит от значения элемента forRemoval аннотации:
-
Если
forRemoval=false(по умолчанию), то элемент программы обычно устарел.Элемент программы, обычно считающийся устаревшим, не планируется к удалению в будущих выпусках, но тем не менее программисты должны перейти к его использованию.
-
Если
forRemoval=true, то элемент программы полностью устарел.Элемент программы, полностью устаревший, планируется к удалению в будущих выпусках. Программисты должны прекратить его использование, чтобы избежать несовместимости исходного и двоичного кода (§13.2) при обновлении до более новой версии.
Компилятор Java должен выдавать предупреждение об устаревании при использовании элемента программы, обычно считающегося устаревшим (переопределение, вызов или ссылка по имени) в объявлении элемента программы (явном или неявном), за исключением случаев:
-
Использование находится в объявлении, которое само по себе устарело, будь то обычно или полностью; или
-
Использование находится в объявлении, которое аннотировано для подавления предупреждений об устаревании; или
-
Использование и объявление находятся в одном внешнем классе; или
-
Использование находится в объявлении
import, которое импортирует обычно устаревший тип или член. -
Использование находится в директиве
exportsилиopens(§7.7.2).
Компилятор Java должен выдавать предупреждение об удалении при использовании элемента программы, полностью устаревшего (переопределение, вызов или ссылка по имени) в объявлении элемента программы (явном или неявном), за исключением случаев:
-
Использование находится в объявлении, которое аннотировано для подавления предупреждений об удалении; или
-
Использование и объявление находятся в одном внешнем классе; или
-
Использование находится в объявлении
import, которое импортирует полностью устаревший тип или член. -
Использование находится в директиве
exportsилиopens.
Полное устаревание достаточно важно, чтобы использование устаревшего элемента вызывало предупреждение об удалении даже если сам используемый элемент устарел, поскольку нет гарантии, что оба элемента будут удалены одновременно. Чтобы отбросить предупреждение, но продолжить использование элемента, программист должен вручную признать риск с помощью аннотации @SuppressWarnings.
Предупреждение об устаревании или предупреждение об удалении не генерируется, когда:
-
используется локальная переменная или формальный параметр (ссылка по имени), даже если объявление локальной переменной или формального параметра аннотировано с помощью
@Deprecated. -
используется имя пакета (ссылка по квалифицированному имени типа или с помощью объявления
import, или директиваexportsилиopens), даже если объявление пакета аннотировано с помощью@Deprecated. -
имя модуля используется с помощью квалифицированной директивы
exportsилиopens, даже если объявление модуля-друга аннотировано с помощью@Deprecated.
Объявление модуля, которое экспортирует или открывает пакет, обычно контролируется тем же программистом или командой, которые контролируют объявление пакета. Поэтому мало пользы в предупреждении, что объявление пакета аннотировано с помощью @Deprecated, когда пакет экспортирован или открыт объявлением модуля. Напротив, объявление модуля, которое экспортирует или открывает пакет дружественному модулю, обычно не контролируется тем же программистом или командой, которые контролируют модуль-друга. Простое экспортирование или открытие пакета не заставляет объявление модуля полагаться на модуль-друга, поэтому мало смысла в выдаче предупреждения, если модуль-друг устарел; программист, создавший объявление модуля, почти всегда захочет подавить такое предупреждение.
Единственное неявное объявление, которое может вызвать предупреждение об устаревании или предупреждение об удалении, — это аннотация контейнера (§9.7.5). Иными словами, если T — это тип аннотации, допускающий повторение, а TC — это тип аннотации-контейнера, и TC устарел, то повторение аннотации @T вызовет предупреждение. Предупреждение связано с неявной аннотацией-контейнером @TC. Не рекомендуется устаревать типом аннотации-контейнера, не устаревая соответствующим типом аннотации, допускающей повторение.
Параметр переменной арности с типом элемента, не допускающим реификацию (§4.7), может вызвать загрязнение кучи (§4.12.2) и привести к предупреждениям о неопределённых преобразованиях во время компиляции (§5.1.9). Такие предупреждения неинформативны, если тело метода с параметром переменной арности работает надлежащим образом.
Тип аннотации SafeVarargs, когда используется для аннотации объявления метода или конструктора, выражает утверждение программиста, которое предотвращает сообщение компилятором Java предупреждений о неопределённых преобразованиях для объявления или вызова метода или конструктора с параметром переменной арности, где компилятор в противном случае сделал бы это из-за того, что тип элемента параметра переменной арности не допускает реификацию.
Аннотация @SafeVarargs имеет внелокальные эффекты, так как подавляет предупреждения о неопределённых преобразованиях в выражениях вызова метода, а также предупреждение об неопределённых преобразованиях в отношении самого объявления метода с параметром переменной арности (§8.4.1). В отличие от этого, аннотация @SuppressWarnings("unchecked") имеет локальные эффекты, потому что она подавляет только предупреждения об неопределённых преобразованиях, относящиеся к объявлению метода.
Каноническая цель для @SafeVarargs — это метод, похожий на java.util.Collections.addAll, объявление которого начинается так:
public static <T> boolean addAll(Collection<? super T> c, T... elements)
Параметр переменной арности имеет объявленный тип T[], который не допускает реификацию. Однако метод в основном только считывает входной массив и добавляет элементы в коллекцию, что являются безопасными операциями по отношению к массиву. Поэтому любые предупреждения о неопределённых преобразованиях во время компиляции при вызове метода java.util.Collections.addAll, вероятно, являются ложными и неинформативными. Применение @SafeVarargs к объявлению метода предотвращает создание этих предупреждений о неопределённых преобразованиях при вызовах метода.
Если объявление фиксированного метода или конструктора аннотировано аннотацией @SafeVarargs, это ошибка времени компиляции.
Если объявление метода с параметром переменной арности не является аннотированным static, final или private, то аннотация @SafeVarargs — это ошибка времени компиляции.
Так как @SafeVarargs применима только к static методам, final и/или private методам экземпляров и конструкторам, аннотация не может использоваться при переопределении методов. Наследование аннотаций работает только для аннотаций классов (а не методов, интерфейсов или конструкторов), поэтому аннотация типа @SafeVarargs не может передаваться через методы экземпляров в классах или через интерфейсы.
Тип аннотации java.lang.annotation.Repeatable используется в объявлении типа аннотации, допускающего повторение для указания его типа аннотации-контейнера (§9.6.3).
Обратите внимание, что мета-аннотация @Repeatable в объявлении T, указывающая TC, не достаточна для того, чтобы TC стал типом аннотации-контейнера для T. Существует множество правил корректности для TC, чтобы быть признанным типом аннотации-контейнера для T.
Тип аннотации FunctionalInterface используется для обозначения интерфейса как функционального интерфейса (§9.8). Это облегчает раннее обнаружение неподходящих объявлений методов, присутствующих в интерфейсе или унаследованных от него, если этот интерфейс предназначен быть функциональным.
Если интерфейс объявлен с аннотацией @FunctionalInterface, но на самом деле не является функциональным интерфейсом, то это ошибка времени компиляции.
Поскольку некоторые интерфейсы функциональны по сути, не обязательно, и нежелательно, чтобы все объявления функциональных интерфейсов аннотировались с помощью @FunctionalInterface.
Аннотация — это метка, которая ассоциирует информацию с конструкцией программы, но не имеет эффекта во время выполнения. Аннотация обозначает конкретное использование типа аннотации (§9.6) и обычно предоставляет значения для элементов этого типа.
Существует три типа аннотаций. Первый тип является наиболее общим, а другие типы — всего лишь сокращённые формы первого.
Нормальные аннотации описаны в §9.7.1, аннотации-метки — в §9.7.2, а аннотации с одним элементом — в §9.7.3. Аннотации могут появляться в различных синтаксических позициях в программе, как описано в §9.7.4. Количество аннотаций одного типа, которые могут появиться в одной позиции, определяется типом аннотации, как описано в §9.7.5.
Обычная аннотация указывает имя типа аннотации и, необязательно, список пар имя-значение элемента, разделённых запятыми. Каждая пара содержит значение элемента, связанное с элементом типа аннотации (§9.6.1).
Обратите внимание, что знак «@» (@) является отдельным токеном (§3.11). Разрешается вставлять пробелы между ним и TypeName, но это не рекомендуется в стиле оформления.
TypeName указывает тип аннотации, соответствующий аннотации. Говорят, что аннотация «относится к» этому типу.
TypeName должен называть доступный тип аннотации (§6.6), иначе происходит ошибка во время компиляции.
Идентификатор в паре «имя-значение элемента» должен быть простым именем одного из элементов (то есть методов) типа аннотации; в противном случае происходит ошибка во время компиляции.
Тип возвращаемого значения этого метода определяет тип элемента пары «имя-значение элемента».
Если тип элемента является типом массива, то для указания значения элемента пары «имя-значение элемента» необязательно использовать фигурные скобки. Если значение элемента не является ИнициализаторМассиваЗначенийЭлементов, то сопоставляется значение массива, единственный элемент которого — это значение элемента. Если значение элемента является ИнициализаторМассиваЗначенийЭлементов, то сопоставляется значение массива, представленное ИнициализаторМассиваЗначенийЭлементов.
Возникает ошибка во время компиляции, если тип элемента не совместим со значением элемента. Тип элемента T совместим со значением элемента V тогда и только тогда, когда выполняется одно из следующих условий:
-
T является типом массива E
[], и выполняется одно из следующих условий:-
Если
Vявляется УсловнымВыражением или Аннотацией, тоVсовместим с E; или -
Если
Vявляется ИнициализаторМассиваЗначенийЭлементов, то каждое значение элемента, содержащееся вV, совместимо с E.ИнициализаторМассиваЗначенийЭлементов аналогичен обычной инициализации массива (§10.6), за исключением того, что ИнициализаторМассиваЗначенийЭлементов может синтаксически содержать аннотации, а также выражения и вложенные инициализаторы. Однако, вложенные инициализаторы не являются семантически допустимыми в ИнициализаторМассиваЗначенийЭлементов, поскольку они никогда не совместимы с элементами массива в объявлениях типов аннотации (вложенные типы массивов недопустимы).
-
-
T не является типом массива, и тип
Vсовместим по присваиванию (§5.2) с T, и:
Обратите внимание, что если T не является типом массива или типом аннотации, значение элемента должно быть условным выражением (§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.6.1).
@ ТипИмени Это сокращение для обычной аннотации:
@TypeName()
Допускается использование аннотаций-маркеров для типов аннотаций с элементами, при условии, что все элементы имеют значения по умолчанию (§9.6.2).
Пример 9.7.2-1. Аннотации-маркеры
Вот пример использования типа аннотации-маркера Preliminary из §9.6.1:
@Preliminary public class TimeTravel { ... }
Аннотация с одним элементом — это сокращение, предназначенное для использования с типами аннотаций с одним элементом (§9.6.1).
Это сокращение для обычной аннотации:
@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.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), но это чисто синтаксический вопрос. Применимость аннотации к объявлению или типу объявляемой сущности — и, таким образом, является ли аннотация аннотацией объявления или аннотацией типа — зависит от применимости типа аннотации:
-
Если тип аннотации применим в контексте объявления, соответствующему объявлению, и не применим в контекстах типов, то аннотация считается применимой только к объявлению.
-
Если тип аннотации применим в контекстах типов, и не применим в контексте объявления, соответствующем объявлению, то аннотация считается применимой только к типу, который ближе всего к аннотации.
-
Если тип аннотации применим в контексте объявления, соответствующему объявлению, и в контекстах типов, то аннотация считается применимой как к объявлению, так и к типу, который ближе всего к аннотации.
В двух последних случаях тип, который ближе всего к аннотации, определяется следующим образом:
-
Если аннотация появляется перед объявлением метода
voidили объявлением локальной переменной, использующейvar(§14.4, §14.14.2, §14.20.3), то ближайшего типа нет. Если тип аннотации считается применимым только к типу, который ближе всего к аннотации, возникает ошибка времени компиляции. -
Если аннотация появляется перед объявлением конструктора, то ближайшим типом является тип нового создаваемого объекта. Тип нового создаваемого объекта — это полное полное имя типа, непосредственно окружающего объявление конструктора. В рамках этого полного квалифицированного имени аннотация применяется к простому имени типа, указанному в объявлении конструктора.
-
Во всех остальных случаях ближайший тип — это тип, записанный в исходном коде для объявляемой сущности; если этот тип является типом массива, то тип элемента считается ближайшим к аннотации.
Например, в объявлении поля
@Foo public static String f;, типом, который ближе всего к@Foo, являетсяString. (Если тип объявления поля был записан какjava.lang.String, тоjava.lang.Stringбыл бы типом, ближайшим к@Foo, и последующие правила запрещали бы аннотацию типа от применения к имени пакетаjava.) В общем объявлении метода@Foo <T> int[] m() {...}, тип, записанный для объявляемой сущности, — этоint[], поэтому@Fooприменяется к типу элементаint.Объявления локальных переменных, которые не используют
var, аналогичны объявлениям параметров формальных выражений лямбда-выражений, поскольку оба допускают аннотации объявления и аннотации типа в исходном коде, но только аннотации типа могут быть сохранены в файлеclass.
Ошибка времени компиляции, если аннотация типа T синтаксически является модификатором:
-
объявления модуля, но T не применим к объявлениям модулей.
-
объявления пакета, но T не применим к объявлениям пакетов.
-
объявления класса, интерфейса или перечисления, но T не применим к объявлениям типов или контекстам типов; или объявления типа аннотации, но T не применим к объявлениям типов аннотаций или контекстам типов.
-
объявления метода (включая элемент типа аннотации), но T не применим к объявлениям методов или контекстам типов.
-
объявления конструктора, но T не применим к объявлениям конструкторов или контекстам типов.
-
объявления параметра типа обобщённого класса, интерфейса, метода или конструктора, но T не применим к объявлениям параметров типа или контекстам типов.
-
объявления поля (включая константу перечисления), но T не применим к объявлениям полей или контекстам типов.
-
объявления формального или исключительного параметра, но T не применим ни к формальным, ни к исключительным параметрам или контекстам типов.
-
объявления параметра получателя, но T не применим к контекстам типов.
-
объявления локальной переменной (включая переменную цикла оператора
forили переменную ресурса оператораtry-с-ресурсами), но T не применим к объявлениям локальных переменных или контекстам типов.
Пять из этих девяти пунктов упоминают "... или контексты типов", потому что они характеризуют пять синтаксических позиций, где аннотация может быть применимой к объявлению или типу объявляемой сущности. Кроме того, два из девяти пунктов — для объявления класса, интерфейса, перечисления и типа аннотации, и для объявления параметра типа — упоминают "... или контексты типов", потому что может быть удобно применять аннотацию, тип которой помечен аннотацией @Target(ElementType.TYPE_USE) (следовательно, применимая в контекстах типов) к объявлению типа.
Аннотация типа допустима, если оба следующих утверждения верны:
-
Простое имя, к которому аннотация ближе всего, классифицируется как имя типа, а не имя пакета.
-
Если простое имя, к которому аннотация ближе всего, следует за "
." и другим именем типа — то есть, аннотация появляется как@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 применима в контексте объявления поля.
Ошибка компиляции, если в контексте объявления или контексте типа появляются несколько аннотаций одного типа 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 является повторяемой с собственным содержащим типом аннотации. Нежелательно повторять аннотации, которые сами являются контейнерами, когда присутствует аннотация базового повторяемого типа.
Функциональный интерфейс — это интерфейс, имеющий ровно один абстрактный метод (помимо методов Object), и, следовательно, представляющий один контракт функции. Этот «один» метод может принимать вид нескольких абстрактных методов с эквивалентными по переопределению сигнатурами, унаследованными от суперинтерфейсов; в этом случае унаследованные методы логически представляют собой один метод.
Для интерфейса I, пусть M — это множество abstract методов, которые являются членами I, но не имеют такой же сигнатуры, как любой метод класса Object (§4.3.2). Тогда I является функциональным интерфейсом, если существует метод m в M, для которого оба следующих условия истинны:
В дополнение к обычному процессу создания экземпляра интерфейса путем объявления и создания класса (§15.9), экземпляры функциональных интерфейсов могут быть созданы с использованием выражений ссылки на метод и лямбда-выражений (§15.13, §15.27).
Определение функционального интерфейса исключает методы в интерфейсе, которые также являются методами public в Object. Это позволяет функционально рассматривать интерфейс, подобный java.util.Comparator<T>, который объявляет несколько методов abstract, из которых только один действительно «новый» - int compare(T,T). Другой - boolean equals(Object) - является явным объявлением метода abstract, который в противном случае был бы неявно объявлен в интерфейсе (§9.2) и автоматически реализован каждым классом, который implements интерфейс.
Обратите внимание, что если не-public методы Object, такие как clone(), явно объявлены в интерфейсе как public, они не автоматически реализуются каждым классом, который implements интерфейс. Реализация, унаследованная от Object, является protected, в то время как метод интерфейса является public, поэтому единственный способ реализации интерфейса заключается в том, чтобы класс переопределил метод Object, не являющийся public, с методом 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>, которые являются типами функциональных интерфейсов, возможны.
Объявление функционального интерфейса позволяет использовать тип функционального интерфейса в программе. Существует четыре типа функционального интерфейса:
В особых случаях полезно рассматривать тип пересечения как тип функционального интерфейса. Обычно это будет выглядеть как пересечение типа функционального интерфейса с одним или несколькими типами интерфейсов-маркеров, такими как Runnable &
. Такое пересечение может использоваться в приведениях типов (§15.16), которые заставляют лямбда-выражение соответствовать определенному типу. Если один из типов интерфейсов в пересечении является java.io.Serializablejava.io.Serializable, запускается специальная поддержка выполнения для сериализации (§15.27.4).
Тип функции функционального интерфейса I — это тип метода (§8.2), который может использоваться для переопределения (§8.4.8) абстрактных методов интерфейса I.
Пусть M — множество абстрактных методов, определенных для I. Тип функции для I состоит из следующего:
-
Параметры типа, типы формальных параметров и тип возвращаемого значения:
Пусть
m— метод вMс:-
сигнатурой, являющейся подсигнатурой сигнатуры каждого метода в
M; и -
типом возвращаемого значения R (возможно,
void), где либо R совпадает с типом возвращаемого значения каждого метода вM, либо R — тип ссылки и является подтипом типа возвращаемого значения каждого метода вM(после адаптации для любых параметров типа (§8.4.4), если у двух методов одинаковая сигнатура).
Если такого метода не существует, то пусть
m— метод вMс:-
сигнатурой, являющейся подсигнатурой сигнатуры каждого метода в
M; и -
типом возвращаемого значения, для которого
mподставляется (§8.4.5) для каждого метода вM.
Параметры типа, типы формальных параметров и тип возвращаемого значения типа функции задаются значением
m. -
-
throwsчасть:throwsчасть типа функции получена изthrowsчастей методов вMследующим образом:-
Если тип функции обобщённый, то
throwsчасти сначала адаптируются к параметрам типа типа функции (§8.4.4).Если тип функции не обобщённый, но хотя бы один метод в
Mобобщённый, тоthrowsчасти сначала удаляются. -
Затем,
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 — джойлдвайлдкард с верхней границей
?extendsUi, то Ti = glb(Ui, Bi) (§5.1.10). -
Если Ai — джойлдвайлдкард с нижней границей
?superLi, то 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 ), ->
booleanPredicate<Number> (тип функции Number ) и -> booleanPredicate<Object> (тип функции Object ) является -> booleanPredicate<? 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.