Аннотации
Аннотации — это способ добавления метаданных к коду. Чтобы объявить аннотацию, поместите модификатор 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)Перечисления
Другие аннотации
Массивы указанных выше типов
Параметры аннотации не могут иметь типы с возможностью null, так как 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) }
Цели применения аннотаций
При аннотировании свойства или параметра первичного конструктора существует несколько элементов Java, сгенерированных из соответствующего элемента Kotlin, и, следовательно, несколько возможных мест для аннотации в сгенерированном байт-коде Java. Чтобы указать, как именно должна быть сгенерирована аннотация, используйте следующий синтаксис:
class Example(@field:Ann val foo, // annotate Java field
@get:Ann val bar, // annotate Java getter
@param:Ann val quux) // annotate Java constructor parameter
Тот же синтаксис можно использовать для аннотирования всего файла. Для этого поместите аннотацию с целевым значением file на верхнем уровне файла, перед директивой пакета или перед всеми импортами, если файл находится в пакете по умолчанию:
@file:JvmName("Foo")
package org.jetbrains.demo
Если у вас есть несколько аннотаций с одинаковой целью, вы можете избежать повторения цели, добавив скобки после цели и поместив все аннотации внутрь скобок:
class Example {
@set:[Inject VisibleForTesting]
var collaborator: Collaborator
}
Полный список поддерживаемых целей применения аннотаций:
fileproperty(аннотации с этой целью не видны для Java)fieldget(получатель свойства)set(установщик свойства)receiver(параметр получателя расширяющей функции или свойства)param(параметр конструктора)setparam(параметр установщика свойства)delegate(поле, хранящее экземпляр делегата для делегированного свойства)
Для аннотирования параметра получателя расширяющей функции используйте следующий синтаксис:
fun @receiver:Fancy String.myExtension() { ... }
Если вы не указываете цель применения аннотации, цель выбирается в соответствии с @Target аннотацией используемой аннотации. Если применимых целей несколько, используется первая применимая цель из следующего списка:
parampropertyfield
Аннотации 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 имеет тип массива, он становится параметром vararg в Kotlin:
// 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 среди своих Kotlin-целей, аннотация отображается на java.lang.annotation.ElementType.TYPE_USE в списке её Java-целей. Это аналогично тому, как TYPE_PARAMETER Kotlin-цель отображается на java.lang.annotation.ElementType.TYPE_PARAMETER Java-цель. Это проблема для Android-клиентов с уровнями API меньше 26, у которых нет этих целей в API.
Чтобы избежать генерации целей аннотаций TYPE_USE и TYPE_PARAMETER, используйте новый аргумент компилятора -Xno-new-java-annotation-targets.
Повторяемые аннотации
Так же, как в Java, Kotlin поддерживает повторяемые аннотации, которые могут применяться к одному элементу кода несколько раз. Чтобы сделать вашу аннотацию повторяемой, отметьте её объявление с помощью @kotlin.annotation.Repeatable мета-аннотации. Это позволит использовать её как повторяемую в Kotlin и Java. Повторяемые аннотации Java также поддерживаются со стороны Kotlin.
Основное отличие от схемы, используемой в 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–2023 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/annotations.html