Аннотации
Аннотации — это теги, с помощью которых можно добавлять метаданные к элементам кода. Инструменты и фреймворки обрабатывают эти метаданные во время компиляции и выполнения, выполняя на их основе различные действия.
С помощью аннотаций можно упростить и автоматизировать выполнение стандартных задач, таких как генерация шаблонного кода, обеспечение соблюдения стандартов кодирования или написание документации.
Объявление
Аннотации — это особый тип класса. Чтобы объявить аннотацию, используйте ключевое слово annotation перед объявлением класса:
annotation class Fancy
Дополнительные атрибуты аннотации можно указать, снабдив класс аннотации метааннотациями:
@Targetзадаёт возможные типы элементов, которые можно снабдить этой аннотацией (например, классы, функции, свойства и выражения);@Retentionзадаёт, сохраняется ли аннотация в скомпилированных файлах классов и доступна ли она через рефлексию во время выполнения (по умолчанию оба значения равны true);@Repeatableпозволяет использовать одну и ту же аннотацию несколько раз для одного элемента;@MustBeDocumentedуказывает, что аннотация является частью публичного API и должна включаться в сигнатуру класса или метода, отображаемую в сгенерированной документации API.
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION,
AnnotationTarget.TYPE_PARAMETER, AnnotationTarget.VALUE_PARAMETER,
AnnotationTarget.EXPRESSION)
@Retention(AnnotationRetention.SOURCE)
@MustBeDocumented
annotation class Fancy
Использование
@Fancy class Foo {
@Fancy fun baz(@Fancy foo: Int): Int {
return (@Fancy 1)
}
}
Чтобы снабдить аннотацией первичный конструктор класса, добавьте ключевое слово constructor в объявление конструктора и укажите аннотации перед ним:
class Foo @Inject constructor(dependency: MyDependency) { ... }
Аннотациями также можно снабдить геттеры и сеттеры свойств:
class Foo {
var x: MyDependency? = null
@Inject set
}
Конструкторы
У аннотаций могут быть конструкторы с параметрами.
annotation class Special(val why: String)
@Special("example") class Foo {}
Допустимые типы параметров:
Типы, соответствующие примитивным типам Java (Int, Long и т. д.)
Строки
Классы (
Foo::class)Перечисления
Другие аннотации
Массивы перечисленных выше типов
Параметры аннотаций не могут иметь nullable-типы, поскольку JVM не поддерживает хранение null в качестве значения атрибута аннотации.
Если аннотация используется в качестве параметра другой аннотации, перед её именем не ставится символ @:
annotation class ReplaceWith(val expression: String)
annotation class Deprecated(
val message: String,
val replaceWith: ReplaceWith = ReplaceWith(""))
@Deprecated("This function is deprecated, use === instead", ReplaceWith("this === other"))
Если в качестве аргумента аннотации нужно указать класс, используйте класс Kotlin (KClass). Компилятор Kotlin автоматически преобразует его в класс Java, поэтому код Java сможет обычным образом обращаться к аннотациям и аргументам.
import kotlin.reflect.KClass annotation class Ann(val arg1: KClass<*>, val arg2: KClass<out Any>) @Ann(String::class, Int::class) class MyClass
Создание экземпляров
В Java тип аннотации представляет собой разновидность интерфейса, поэтому его можно реализовать и использовать экземпляр. В качестве альтернативы этому механизму Kotlin позволяет вызывать конструктор класса аннотации в произвольном коде и аналогичным образом использовать полученный экземпляр.
annotation class InfoMarker(val info: String)
fun processInfo(marker: InfoMarker): Unit = TODO()
fun main(args: Array<String>) {
if (args.isNotEmpty())
processInfo(getAnnotationReflective(args))
else
processInfo(InfoMarker("default"))
}
Подробнее о создании экземпляров классов аннотаций см. в этом предложении KEEP.
Лямбда-выражения
Аннотации также можно использовать для лямбда-выражений. Они будут применяться к методу invoke(), в который компилируется тело лямбда-выражения. Это полезно для таких фреймворков, как Quasar, использующих аннотации для управления параллелизмом.
annotation class Suspendable
val f = @Suspendable { Fiber.sleep(10) }
Цели применения аннотаций
При добавлении аннотации к свойству или параметру первичного конструктора из соответствующего элемента Kotlin генерируется несколько элементов Java, поэтому в сгенерированном байт-коде Java есть несколько возможных мест для аннотации. Чтобы точно указать, как должна генерироваться аннотация, используйте следующий синтаксис:
class Example(@field:Ann val foo, // annotate only the Java field
@get:Ann val bar, // annotate only the Java getter
@param:Ann val quux) // annotate only the Java constructor parameter
Этот синтаксис можно также использовать для аннотации всего файла. Для этого поместите аннотацию с целью file на верхнем уровне файла — перед директивой package или перед всеми импортами, если файл находится в пакете по умолчанию:
@file:JvmName("Foo")
package org.jetbrains.demo
Если у вас есть несколько аннотаций с одной и той же целью, можно не повторять цель: добавьте после неё скобки и поместите все аннотации внутрь (за исключением метацели all):
class Example {
@set:[Inject VisibleForTesting]
var collaborator: Collaborator
}
Полный список поддерживаемых целей применения аннотаций:
filefieldproperty(аннотации с этой целью невидимы для Java)get(геттер свойства)set(сеттер свойства)all(метацель для свойств; дополнительную информацию см. в разделе о метацелиall)-
receiver(параметр-приёмник функции или свойства-расширения)Чтобы снабдить аннотацией параметр-приёмник функции-расширения, используйте следующий синтаксис:
fun @receiver:Fancy String.myExtension() { ... } param(параметр конструктора)setparam(параметр сеттера свойства)delegate(поле, в котором хранится экземпляр делегата для делегированного свойства)
Значения по умолчанию, если цели применения не указаны
Если цель применения не указана, компилятор выбирает её в соответствии с аннотацией @Target, указанной для используемой аннотации. Если подходит несколько целей, компилятор выбирает одну или несколько из них в следующем порядке:
Цель параметра конструктора (
param).Цель свойства (
property).Цель поля (
field), если она применима, а цель свойства (property) — нет.
Если ни одна из целей param, property или field не применима, аннотация недопустима, и необходимо явно указать цель её применения.
Рассмотрим аннотацию @Email из Jakarta Bean Validation:
@Target(value={METHOD,FIELD,ANNOTATION_TYPE,CONSTRUCTOR,PARAMETER,TYPE_USE})
public @interface Email { }
Рассмотрим следующий пример с этой аннотацией:
data class User(val username: String,
// @Email is now equivalent to @param:Email @field:Email
@Email val email: String) {
// @Email is still equivalent to @field:Email
@Email val secondaryEmail: String? = null
}
В этом примере аннотация @Email применяется и к параметру конструктора, и к полю свойства email, поскольку это свойство:
Объявлено в первичном конструкторе.
Не имеет пользовательского геттера или сеттера, поэтому компилятор генерирует поле для хранения значения.
Аннотация @Email применяется только к полю свойства secondaryEmail, поскольку это свойство:
Не объявлено в первичном конструкторе.
Не имеет пользовательского геттера или сеттера, поэтому компилятор генерирует поле для хранения значения.
Метацель all
Цель all упрощает применение одной и той же аннотации не только к параметру и свойству или полю, но и к соответствующим геттеру и сеттеру.
В частности, аннотация с меткой all распространяется на следующие элементы, если это применимо:
На параметр конструктора (
param), если свойство определено в первичном конструкторе.На само свойство (
property).На поле для хранения значения (
field), если оно есть у свойства.На геттер (
get).На параметр сеттера (
setparam), если свойство объявлено какvar.На цель
RECORD_COMPONENT, доступную только в Java, если класс снабжён аннотацией@JvmRecord.
Рассмотрим аннотацию @Email из Jakarta Bean Validation, которая определена следующим образом:
@Target(value={METHOD,FIELD,ANNOTATION_TYPE,CONSTRUCTOR,PARAMETER,TYPE_USE})
public @interface Email { }
В приведённом ниже примере эта аннотация @Email применяется ко всем соответствующим целям:
data class User(
val username: String,
// Applies @Email to param, field, and get
@all:Email val email: String,
// Applies @Email to param, field, get, and setparam
@all:Email var name: String,
) {
// Applies @Email to field and getter (not param since it's not in the constructor)
@all:Email val secondaryEmail: String? = null
}
Метацель all можно использовать с любым свойством, как внутри первичного конструктора, так и за его пределами.
Ограничения
Цель all имеет некоторые ограничения:
Аннотация не распространяется на типы, потенциальные приёмники-расширения, контекстные приёмники или параметры.
-
Её нельзя использовать с несколькими аннотациями:
@all:[A B] // forbidden, use @all:A @all:B val x: Int = 5
Её нельзя использовать с делегированными свойствами.
Аннотации Java
Аннотации Java полностью совместимы с Kotlin:
import org.junit.Test
import org.junit.Assert.*
import org.junit.Rule
import org.junit.rules.*
class Tests {
// apply @Rule annotation to property getter
@get:Rule val tempFolder = TemporaryFolder()
@Test fun simple() {
val f = tempFolder.newFile()
assertEquals(42, getTheAnswer())
}
}
Поскольку порядок параметров аннотации, написанной на Java, не определён, для передачи аргументов нельзя использовать обычный синтаксис вызова функции. Вместо этого необходимо использовать синтаксис именованных аргументов:
// Java
public @interface Ann {
int intValue();
String stringValue();
}
// Kotlin @Ann(intValue = 1, stringValue = "abc") class C
Как и в Java, особый случай — параметр value: его значение можно указать без явного имени:
// Java
public @interface AnnWithValue {
String value();
}
// Kotlin
@AnnWithValue("abc") class C
Массивы в качестве параметров аннотаций
Если аргумент value в Java имеет тип массива, в Kotlin он становится параметром vararg:
// Java
public @interface AnnWithArrayValue {
String[] value();
}
// Kotlin
@AnnWithArrayValue("abc", "foo", "bar") class C
Для других аргументов, имеющих тип массива, необходимо использовать синтаксис литерала массива или arrayOf(...):
// Java
public @interface AnnWithArrayMethod {
String[] names();
}
@AnnWithArrayMethod(names = ["abc", "foo", "bar"]) class C
Доступ к свойствам экземпляра аннотации
Значения экземпляра аннотации доступны в коде Kotlin как свойства:
// Java
public @interface Ann {
int value();
}
// Kotlin
fun foo(ann: Ann) {
val i = ann.value
}
Возможность не генерировать цели аннотаций JVM 1.8+
Если среди целей Kotlin-аннотации есть TYPE, в список целей Java-аннотации добавляется java.lang.annotation.ElementType.TYPE_USE. Это аналогично тому, как цель Kotlin TYPE_PARAMETER соответствует цели Java java.lang.annotation.ElementType.TYPE_PARAMETER. Это создаёт проблему для клиентов Android с уровнем API ниже 26, в API которых нет этих целей.
Чтобы не генерировать цели аннотаций TYPE_USE и TYPE_PARAMETER, используйте новый аргумент компилятора -Xno-new-java-annotation-targets.
Повторяемые аннотации
Как и в Java, в Kotlin есть повторяемые аннотации, которые можно применять к одному элементу кода несколько раз. Чтобы сделать аннотацию повторяемой, пометьте её объявление метааннотацией @kotlin.annotation.Repeatable. Это сделает её повторяемой и в Kotlin, и в Java. Kotlin также поддерживает повторяемые аннотации Java.
Главное отличие от схемы, используемой в Java, — отсутствие содержащей аннотации, которую компилятор Kotlin генерирует автоматически, используя заранее заданное имя. Для аннотации из примера ниже он сгенерирует содержащую аннотацию @Tag.Container:
@Repeatable annotation class Tag(val name: String) // The compiler generates the @Tag.Container containing annotation
Чтобы задать собственное имя содержащей аннотации, примените метааннотацию @kotlin.jvm.JvmRepeatable и передайте в качестве аргумента явно объявленный класс содержащей аннотации:
@JvmRepeatable(Tags::class) annotation class Tag(val name: String) annotation class Tags(val value: Array<Tag>)
Чтобы извлечь повторяемые аннотации Kotlin или Java с помощью рефлексии, используйте функцию KAnnotatedElement.findAnnotations().
Подробнее о повторяемых аннотациях Kotlin см. в этом предложении KEEP.
© 2010–2026 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/annotations.html