Spec-Zone.ru › Kotlin 2

Аннотации

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

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

Для разработки собственных процессоров аннотаций можно использовать API обработки символов Kotlin (KSP).

Объявление

Аннотации — это особый тип класса. Чтобы объявить аннотацию, используйте ключевое слово 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
}

Полный список поддерживаемых целей применения аннотаций:

  • file

  • field

  • property (аннотации с этой целью невидимы для 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.

18 мая 2026 г.
Пакеты и импортыМодификаторы видимости

© 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

Spec-Zone.ru

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