Аннотации

Объявление аннотаций

Аннотации являются специальной формой синтаксических метаданных, добавленных в исходный код. Для объявления аннотации используйте модификатор annotation перед именем класса:

annotation class Fancy

Дополнительные атрибуты аннотаций могут быть определены путём аннотации класса-аннотации мета-аннотациями:

  • @Target определяет возможные виды элементов, которые могут быть помечены аннотацией (классы, функции, свойства, выражения и т.д.);
  • @Retention определяет, будет ли аннотация храниться в скомпилированном классе и будет ли видима через рефлексию (по умолчанию оба утверждения верны);
  • @Repeatable позволяет использовать одну и ту же аннотацию на одном элементе несколько раз;
  • @MustBeDocumented определяет то, что аннотация является частью публичного API и должна быть включена в сигнатуру класса или метода, попадающую в сгенерированную документацию.
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION,
        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). Компилятор Kotin автоматически сконвертирует его в Java класс, так что код на Java сможет видеть аннотации и их аргументы.


import kotlin.reflect.KClass

annotation class Ann(val arg1: KClass<*>, val arg2: KClass<out Any?>)

@Ann(String::class, Int::class) class MyClass

Лямбды

Аннотации также можно использовать с лямбдами. Они будут применены к 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();
}
// Kotlin
@AnnWithArrayMethod(names = arrayOf("abc", "foo", "bar")) class C

Значения экземпляра аннотации становятся свойствами в Kotlin-коде.

// Java
public @interface Ann {
    int value();
}
// Kotlin
fun foo(ann: Ann) {
    val i = ann.value
}