Аннотации
Аннотации являются специальной формой синтаксических метаданных, добавленных в исходный код.
Для объявления аннотации используйте модификатор annotation
перед именем класса.
annotation class Fancy
Дополнительные атрибуты аннотаций могут быть определены путём аннотации класса-аннотации мета-аннотациями:
@Target
определяет возможные виды элементов, которые могут быть помечены аннотацией (такие как классы, функции, свойства и выражения);@Retention
определяет, будет ли аннотация храниться в скомпилированном классе и будет ли видима через рефлексию (по умолчанию оба утверждения верны);@Repeatable
позволяет использовать одну и ту же аннотацию на одном элементе несколько раз;@MustBeDocumented
определяет то, что аннотация является частью публичного 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("пример") class Foo {}
Разрешены параметры следующих типов:
- типы, которые соответствуют примитивам Java (Int, Long и т.д.),
- строки,
- классы (
Foo::class
), - перечисляемые типы,
- другие аннотации,
- массивы, содержащие значения приведённых выше типов.
Параметры аннотаций не могут иметь обнуляемые типы, потому что JVM не поддерживает хранение null
в качестве значения
атрибута аннотации.
Если аннотация используется в качестве параметра к другой аннотации, её имя не нужно начинать со знака @
.
annotation class ReplaceWith(val expression: String)
annotation class Deprecated(
val message: String,
val replaceWith: ReplaceWith = ReplaceWith(""))
@Deprecated("Эта функция устарела, вместо неё используйте ===", 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"))
}
Создание экземпляров классов аннотаций для Kotlin/Native пока недоступно.
Узнайте больше о создании экземпляров классов аннотаций в этом KEEP.
Лямбды
Аннотации также можно использовать с лямбдами. Они будут применены к invoke()
методу, в который генерируется тело
лямбды. Это полезно для фреймворков вроде Quasar, которые используют
аннотации для контроля многопоточности.
annotation class Suspendable
val f = @Suspendable { Fiber.sleep(10) }
Аннотации с указаниями
Когда вы помечаете свойство или первичный конструктор аннотацией, из соответствующего Kotlin-элемента генерируются несколько Java-элементов, и поэтому в сгенерированном байт-коде элемент появляется в нескольких местах. Чтобы указать, в каком именно месте аннотация должна быть сгенерирована, используйте следующий синтаксис:
class Example(@field:Ann val foo, // аннотация для Java-поля
@get:Ann val bar, // аннотация для Java-геттера
@param:Ann val quux) // аннотация для параметра конструктора Java
Тот же синтаксис может быть использован для аннотации целого файла. Для этого отметьте аннотацию словом file
и
вставьте её в начале файла: перед указанием пакета или перед импортами, если файл находится в пакете по умолчанию.
@file:JvmName("Foo")
package org.jetbrains.demo
Если вы помечаете несколькими аннотациями одно указание, вы можете избежать повторения путём указания всех аннотаций в квадратных скобках.
class Example {
@set:[Inject VisibleForTesting]
var collaborator: Collaborator
}
Полный список поддерживаемых указаний:
file
property
(такие аннотации не будут видны в Java)field
get
(геттер)set
(сеттер)receiver
(параметр-приёмник расширения)param
(параметр конструктора)setparam
(параметр сеттера)delegate
(поле, которое хранит экземпляр делегата для делегированного свойства)
Чтобы пометить аннотацией параметр-приёмник расширения, используйте следующий синтаксис:
fun @receiver:Fancy String.myExtension() { /* ... */ }
Если вы не сделали указание, аннотация будет применена к элементу, выбранному в соответствии с аннотацией @Target
той
аннотации, которую вы используете. Если существует несколько элементов, к которым возможно применение аннотации, будет
выбран первый подходящий элемент из следующего списка:
param
property
field
Java-аннотации
Java-аннотации на 100% совместимы в Kotlin:
import org.junit.Test
import org.junit.Assert.*
import org.junit.Rule
import org.junit.rules.*
class Tests {
// применение аннотации @Rule к геттеру
@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
}
Повторяющиеся аннотации
Как и в Java, в Kotlin есть повторяющиеся
аннотации, которые можно применять к одному элементу кода несколько раз. Чтобы сделать вашу аннотацию повторяемой,
отметьте ее объявление мета-аннотацией
@kotlin.annotation.Repeatable
. Это
сделает её повторяемой как в Kotlin, так и в Java. Повторяющиеся аннотации Java также поддерживаются со стороны Kotlin.
Основным отличием от схемы, используемой в Java, является отсутствие содержащей аннотации, которую компилятор Kotlin
генерирует автоматически с предопределенным именем. Для аннотации в приведенном ниже примере будет сгенерирована
содержащая аннотация @Tag.Container
.
@Repeatable
annotation class Tag(val name: String)
// Компилятор генерирует содержащую аннотацию @Tag.Container
Вы можете задать имя для содержащей аннотации, применив мета-аннотацию
@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.