--- title: Аннотации description: Объявление аннотаций, цели, retention, use-site targets, чтение через рефлексию, аннотации интеропа tags: - kotlin - annotations - reflection --- Аннотация - метаданные, прикреплённые к объявлению. Сама по себе она ничего не делает: смысл ей придаёт тот, кто её читает - рефлексия в рантайме, процессор аннотаций в компиляции или сам компилятор. ```kotlin import kotlin.reflect.full.findAnnotation @Target(AnnotationTarget.CLASS) @Retention(AnnotationRetention.RUNTIME) annotation class Table(val name: String, val schema: String = "public") @Table("users") class User(val id: Int, val name: String) fun main() { val table = User::class.findAnnotation() println("таблица=${table?.name}, схема=${table?.schema}") } ``` ``` таблица=users, схема=public ``` ## Объявление ```kotlin annotation class Simple annotation class WithParams( val name: String, val priority: Int = 0, val tags: Array = [], ) fun main() { println("объявлены") } ``` ``` объявлены ``` Правила для аннотаций жёстче, чем для обычных классов: - нет тела, нет функций и состояния - только параметры конструктора; - все параметры - `val`; - типы параметров ограничены: примитивы, `String`, `KClass`, `enum`, другая аннотация, массивы перечисленного; - нельзя `null` в качестве значения по умолчанию - сам тип не может быть nullable. ## Цели - @Target ```kotlin @Target( AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY, AnnotationTarget.VALUE_PARAMETER, ) annotation class Marker @Marker class Annotated(@Marker val prop: Int) { @Marker fun method() = Unit } fun main() { println("применено к четырём местам") } ``` ``` применено к четырём местам ``` | Цель | Куда | |---|---| | `CLASS` | класс, интерфейс, `object`, аннотация | | `FUNCTION` | функция, не конструктор | | `PROPERTY` | свойство (только Kotlin-код) | | `FIELD` | backing field свойства | | `VALUE_PARAMETER` | параметр функции или конструктора | | `CONSTRUCTOR` | конструктор | | `PROPERTY_GETTER`, `PROPERTY_SETTER` | аксессоры | | `LOCAL_VARIABLE` | локальная переменная | | `TYPE` | использование типа: `List<@Marker String>` | | `TYPE_PARAMETER` | параметр типа | | `EXPRESSION` | выражение (требует `SOURCE`) | | `FILE` | файл, через `@file:` | | `ANNOTATION_CLASS` | мета-аннотация | Без `@Target` аннотация применима почти везде, кроме `TYPE`, `EXPRESSION`, `FILE` и `TYPE_PARAMETER` - их нужно указывать явно. ## Хранение - @Retention ```kotlin @Retention(AnnotationRetention.SOURCE) @Target(AnnotationTarget.CLASS) annotation class Src @Retention(AnnotationRetention.BINARY) @Target(AnnotationTarget.CLASS) annotation class Bin @Retention(AnnotationRetention.RUNTIME) @Target(AnnotationTarget.CLASS) annotation class Run @Target(AnnotationTarget.CLASS) annotation class NoRetention @Src @Bin @Run class All @NoRetention class Default fun main() { println("All=${All::class.annotations.map { it.annotationClass.simpleName }}") println("Default=${Default::class.annotations.map { it.annotationClass.simpleName }}") } ``` ``` All=[Run] Default=[NoRetention] ``` | Retention | В байт-коде | Видна рефлексии | |---|---|---| | `SOURCE` | нет | нет | | `BINARY` | да | нет | | `RUNTIME` (по умолчанию) | да | да | Ключевое отличие от Java: **по умолчанию `RUNTIME`**, а не `CLASS`. Поэтому `@NoRetention` без явного указания видна через рефлексию - в Java аналогичная аннотация была бы невидима. Для аннотаций, которые читает только KSP или линтер, стоит явно ставить `SOURCE`: это убирает их из байт-кода. ## Use-site targets Свойство в Kotlin компилируется в несколько Java-элементов: поле, геттер, иногда сеттер, а для параметра конструктора ещё и параметр. Аннотация должна попасть на конкретный из них - для этого есть префиксы. ```kotlin import kotlin.reflect.KMutableProperty1 import kotlin.reflect.full.memberProperties import kotlin.reflect.full.primaryConstructor import kotlin.reflect.jvm.javaConstructor import kotlin.reflect.jvm.javaField import kotlin.reflect.jvm.javaGetter import kotlin.reflect.jvm.javaSetter @Target( AnnotationTarget.PROPERTY, AnnotationTarget.FIELD, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER, AnnotationTarget.VALUE_PARAMETER, ) @Retention(AnnotationRetention.RUNTIME) annotation class Mark(val where: String) class Targets( @Mark("без префикса") val implicit: Int, @field:Mark("field") val onField: Int, @get:Mark("getter") val onGetter: Int, @property:Mark("property") val onProperty: Int, ) { @set:Mark("setter") var mutable: Int = 0 } fun main() { for (p in Targets::class.memberProperties.sortedBy { it.name }) { val prop = p.annotations.filterIsInstance().map { it.where } val field = p.javaField?.annotations?.filterIsInstance()?.map { it.where }.orEmpty() val getter = p.javaGetter?.annotations?.filterIsInstance()?.map { it.where }.orEmpty() println("${p.name}: property=$prop field=$field getter=$getter") } val ctor = Targets::class.primaryConstructor!!.javaConstructor!! ctor.parameterAnnotations.forEachIndexed { i, anns -> println("параметр $i: ${anns.filterIsInstance().map { it.where }}") } val mutable = Targets::class.memberProperties.first { it.name == "mutable" } val setter = (mutable as KMutableProperty1<*, *>).javaSetter println("setter=${setter?.annotations?.filterIsInstance()?.map { it.where }}") } ``` ``` implicit: property=[без префикса] field=[] getter=[] mutable: property=[] field=[] getter=[] onField: property=[] field=[field] getter=[] onGetter: property=[] field=[] getter=[getter] onProperty: property=[property] field=[] getter=[] параметр 0: [без префикса] параметр 1: [] параметр 2: [] параметр 3: [] setter=[setter] ``` | Префикс | Куда попадёт | |---|---| | `@property:` | свойство (видно только Kotlin-рефлексии) | | `@field:` | backing field | | `@get:` | геттер | | `@set:` | сеттер | | `@param:` | параметр конструктора | | `@setparam:` | параметр сеттера | | `@receiver:` | получатель функции-расширения | | `@delegate:` | поле делегата | | `@file:` | файл, до объявления `package` | | `@all:` | все применимые цели сразу | ### Правило выбора по умолчанию Без префикса аннотация применяется к **первой подходящей** цели из списка, в порядке: `param` → `property` → `field`. Это проверяется легко: ```kotlin import kotlin.reflect.full.memberProperties import kotlin.reflect.full.primaryConstructor import kotlin.reflect.jvm.javaConstructor import kotlin.reflect.jvm.javaField @Target(AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.PROPERTY, AnnotationTarget.FIELD) @Retention(AnnotationRetention.RUNTIME) annotation class Multi @Target(AnnotationTarget.PROPERTY, AnnotationTarget.FIELD) @Retention(AnnotationRetention.RUNTIME) annotation class PropOrField @Target(AnnotationTarget.FIELD) @Retention(AnnotationRetention.RUNTIME) annotation class FieldOnly class C( @Multi val a: Int, @PropOrField val b: Int, @FieldOnly val c: Int, ) fun report(name: String) { val p = C::class.memberProperties.first { it.name == name } val onProperty = p.annotations.map { it.annotationClass.simpleName } val onField = p.javaField?.annotations?.map { it.annotationClass.simpleName }.orEmpty() val index = C::class.primaryConstructor!!.parameters.indexOfFirst { it.name == name } val onParam = C::class.primaryConstructor!!.javaConstructor!! .parameterAnnotations[index].map { it.annotationClass.simpleName } println("$name: property=$onProperty field=$onField param=$onParam") } fun main() { report("a") report("b") report("c") } ``` ``` a: property=[Multi] field=[] param=[Multi] b: property=[PropOrField] field=[] param=[] c: property=[] field=[FieldOnly] param=[] ``` Обратите внимание на `a`: аннотация, применимая и к параметру, и к свойству, попала **на оба** - на параметр конструктора и на Kotlin-свойство (но не на поле). Именно здесь возникают проблемы с Java-библиотеками: Jackson или Hibernate читают поля через Java Reflection, а там аннотации нет - нужен явный `@field:`. ### @all ```kotlin import kotlin.reflect.full.memberProperties import kotlin.reflect.full.primaryConstructor import kotlin.reflect.jvm.javaConstructor import kotlin.reflect.jvm.javaField import kotlin.reflect.jvm.javaGetter @Target( AnnotationTarget.PROPERTY, AnnotationTarget.FIELD, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.VALUE_PARAMETER, ) @Retention(AnnotationRetention.RUNTIME) annotation class Mark class WithAll(@all:Mark val x: Int) fun main() { val p = WithAll::class.memberProperties.first() println("property=${p.annotations.size}") println("field=${p.javaField?.annotations?.size}") println("getter=${p.javaGetter?.annotations?.size}") val params = WithAll::class.primaryConstructor!!.javaConstructor!!.parameterAnnotations println("param=${params[0].size}") } ``` ``` property=1 field=1 getter=1 param=1 ``` `@all:` - решение давней проблемы: раньше приходилось писать `@field:Mark @get:Mark @param:Mark` подряд. В Kotlin 2.4 работает без дополнительных флагов компилятора. ## Параметры аннотаций ```kotlin import kotlin.reflect.KClass import kotlin.reflect.full.findAnnotation interface Validator class NotBlank : Validator class MaxLength : Validator enum class Mode { FAST, SAFE } @Target(AnnotationTarget.CLASS) @Retention(AnnotationRetention.RUNTIME) annotation class Validators(val value: Array>) @Target(AnnotationTarget.CLASS) @Retention(AnnotationRetention.RUNTIME) annotation class Levels(val value: IntArray, val names: Array) @Target(AnnotationTarget.CLASS) @Retention(AnnotationRetention.RUNTIME) annotation class Nested(val inner: Levels) @Target(AnnotationTarget.CLASS) @Retention(AnnotationRetention.RUNTIME) annotation class WithEnum(val mode: Mode) @Validators([NotBlank::class, MaxLength::class]) @Levels(value = [1, 2, 3], names = ["low", "mid", "high"]) @Nested(Levels([9], ["x"])) @WithEnum(Mode.SAFE) class Configured fun main() { val kc = Configured::class println("классы=${kc.findAnnotation()!!.value.map { it.simpleName }}") val levels = kc.findAnnotation()!! println("IntArray=${levels.value.toList()} names=${levels.names.toList()}") println("вложенная=${kc.findAnnotation()!!.inner.value.toList()}") println("enum=${kc.findAnnotation()!!.mode}") } ``` ``` классы=[NotBlank, MaxLength] IntArray=[1, 2, 3] names=[low, mid, high] вложенная=[9] enum=SAFE ``` Массивы задаются литералом `[a, b]` - старый синтаксис `arrayOf(a, b)` тоже работает, но литерал короче. Классы передаются как `KClass`, то есть `Foo::class`, а не `Foo::class.java`. ## Значения по умолчанию и equals ```kotlin import kotlin.reflect.full.findAnnotation @Target(AnnotationTarget.CLASS) @Retention(AnnotationRetention.RUNTIME) annotation class HasDefaults(val a: Int = 1, val b: String = "x", val c: IntArray = [1, 2]) @HasDefaults class WithDefaults @HasDefaults(a = 5) class Partial fun main() { val d = WithDefaults::class.findAnnotation()!! println("дефолты a=${d.a} b=${d.b} c=${d.c.toList()}") val p = Partial::class.findAnnotation()!! println("частично a=${p.a} b=${p.b}") println("toString=$p") println("равенство=${d == WithDefaults::class.findAnnotation()}") } ``` ``` дефолты a=1 b=x c=[1, 2] частично a=5 b=x toString=@HasDefaults(a=5, b="x", c={1, 2}) равенство=true ``` Экземпляры аннотаций сравниваются по значениям параметров, а `toString` даёт читаемое представление с подставленными дефолтами - удобно для отладки. ## Повторяемые аннотации ```kotlin import kotlin.reflect.full.findAnnotations @Repeatable @Target(AnnotationTarget.CLASS) @Retention(AnnotationRetention.RUNTIME) annotation class Tag(val value: String) @Tag("a") @Tag("b") class Tagged fun main() { println("все теги=${Tagged::class.findAnnotations().map { it.value }}") println("через annotations=${Tagged::class.annotations.filterIsInstance().map { it.value }}") } ``` ``` все теги=[a, b] через annotations=[a, b] ``` В Java для повторяемых аннотаций нужен явный контейнер (`@Repeatable(Tags.class)` плюс класс `Tags` с массивом). В Kotlin достаточно `@Repeatable`, а для чтения есть `findAnnotations()`, возвращающий список. ## Чтение через рефлексию ```kotlin import kotlin.reflect.full.findAnnotation import kotlin.reflect.full.hasAnnotation import kotlin.reflect.full.memberProperties @Target(AnnotationTarget.CLASS) @Retention(AnnotationRetention.RUNTIME) annotation class Table(val name: String) @Target(AnnotationTarget.PROPERTY) @Retention(AnnotationRetention.RUNTIME) annotation class Column(val name: String, val nullable: Boolean = false) @Table("users") class User( @Column("user_id") val id: Int, @Column("full_name", nullable = true) val name: String?, ) fun main() { val kc = User::class println("hasAnnotation=${kc.hasAnnotation
()}") println("findAnnotation=${kc.findAnnotation
()?.name}") for (p in kc.memberProperties.sortedBy { it.name }) { val column = p.findAnnotation() println("${p.name}: column=${column?.name} nullable=${column?.nullable}") } } ``` ``` hasAnnotation=true findAnnotation=users id: column=user_id nullable=false name: column=full_name nullable=true ``` | Функция | Что возвращает | |---|---| | `annotations` | список всех `RUNTIME`-аннотаций элемента | | `findAnnotation()` | первую аннотацию типа `T` или `null` | | `findAnnotations()` | все аннотации типа `T` (для `@Repeatable`) | | `hasAnnotation()` | есть ли аннотация | | `annotationClass` | `KClass` самой аннотации | Эти функции есть у `KClass`, `KProperty`, `KFunction` и `KParameter` - интерфейс единый. Подробнее: [Рефлексия](Рефлексия.md) ## Практический пример: валидация ```kotlin import kotlin.reflect.full.findAnnotation import kotlin.reflect.full.memberProperties import kotlin.reflect.jvm.isAccessible @Target(AnnotationTarget.PROPERTY) @Retention(AnnotationRetention.RUNTIME) annotation class NotBlank(val message: String = "не должно быть пустым") @Target(AnnotationTarget.PROPERTY) @Retention(AnnotationRetention.RUNTIME) annotation class Range(val min: Int, val max: Int) data class Form( @NotBlank val name: String, @NotBlank("email обязателен") val email: String, @Range(18, 120) val age: Int, ) fun validate(obj: Any): List { val errors = mutableListOf() for (p in obj::class.memberProperties) { p.isAccessible = true val value = p.call(obj) p.findAnnotation()?.let { rule -> if ((value as? String).isNullOrBlank()) errors += "${p.name}: ${rule.message}" } p.findAnnotation()?.let { rule -> val number = value as? Int ?: return@let if (number !in rule.min..rule.max) { errors += "${p.name}: должно быть от ${rule.min} до ${rule.max}" } } } return errors.sorted() } fun main() { println(validate(Form("Иван", "ivan@example.com", 30))) println(validate(Form("", "", 5))) } ``` ``` [] [age: должно быть от 18 до 120, email: email обязателен, name: не должно быть пустым] ``` Это рабочая схема, но она платит рефлексией в рантайме: разбор метаданных при первом обращении и потеря проверок на этапе компиляции. Тот же результат даёт KSP-процессор, генерирующий код валидации при сборке. Подробнее: [KSP](../Инструментарий/KSP.md) ## Аннотации компилятора Часть аннотаций обрабатывает сам компилятор - они меняют генерируемый код или проверки. | Аннотация | Что делает | |---|---| | `@Deprecated` | предупреждение или ошибка при использовании | | `@Suppress("NAME")` | подавляет предупреждение в области применения | | `@JvmName` | имя в байт-коде | | `@JvmStatic` | статический метод для членов `companion object` | | `@JvmOverloads` | перегрузки для параметров со значениями по умолчанию | | `@JvmField` | публичное поле вместо геттера | | `@JvmInline` | обязательна для `value class` | | `@Throws` | добавляет `throws` в сигнатуру для Java | | `@OptIn`, `@RequiresOptIn` | механизм согласия на нестабильный API | | `@PublishedApi` | доступ к `internal` из `public inline` | | `@Volatile`, `@Synchronized` | то же, что в Java | | `@Transient` | исключение из сериализации | | `@DslMarker` | ограничение области в DSL | | `@BuilderInference` | вывод типа в билдерах | ```kotlin import kotlin.reflect.full.findAnnotation @Deprecated("используйте new()", ReplaceWith("new()"), DeprecationLevel.WARNING) fun old() = 1 fun new() = 2 fun main() { val d = ::old.findAnnotation() println("message=${d?.message}") println("level=${d?.level}") println("replaceWith=${d?.replaceWith?.expression}") } ``` ``` message=используйте new() level=WARNING replaceWith=new() ``` `ReplaceWith` понимает IDE: она предлагает автозамену вызова. `DeprecationLevel.ERROR` делает использование ошибкой компиляции, `HIDDEN` убирает объявление из автодополнения, сохраняя бинарную совместимость. Аннотации интеропа меняют байт-код заметно: ```kotlin @file:JvmName("InteropUtils") import java.io.IOException import java.lang.reflect.Modifier class Interop { @Throws(IOException::class) fun mayThrow() = Unit @JvmField val field = 1 @get:JvmName("fetchValue") val value = 2 @JvmOverloads fun withDefaults(a: Int, b: Int = 1, c: Int = 2) = a + b + c companion object { @JvmStatic fun staticFn() = 3 } } fun main() { val c = Interop::class.java println("методы=${c.declaredMethods.map { it.name }.sorted()}") println("поля=${c.declaredFields.map { it.name }.sorted()}") println("throws=${c.getDeclaredMethod("mayThrow").exceptionTypes.map { it.simpleName }}") println("статические=${c.declaredMethods.filter { Modifier.isStatic(it.modifiers) }.map { it.name }.sorted()}") } ``` ``` методы=[fetchValue, mayThrow, staticFn, withDefaults, withDefaults, withDefaults, withDefaults$default] поля=[Companion, field, value] throws=[IOException] статические=[staticFn, withDefaults$default] ``` `@JvmOverloads` дал три перегрузки `withDefaults`, `@get:JvmName` переименовал геттер в `fetchValue`, `@JvmField` превратил свойство в поле без геттера. Подробнее: [Kotlin из Java](../Взаимодействие%20с%20Java/Kotlin%20из%20Java.md) ## Аннотации для KSP Аннотация, предназначенная процессору, не нужна в рантайме: ```kotlin @Target(AnnotationTarget.CLASS) @Retention(AnnotationRetention.SOURCE) annotation class GenerateBuilder @GenerateBuilder data class Person(val name: String, val age: Int) fun main() { println("в рантайме=${Person::class.annotations.size}") } ``` ``` в рантайме=0 ``` `SOURCE` убирает аннотацию из байт-кода полностью - KSP читает исходники, а не классы. Это уменьшает размер артефакта и исключает случайное чтение рефлексией. Подробнее: [KSP](../Инструментарий/KSP.md) > [!info] Отличия от Java > | Java | Kotlin | > |---|---| > | `@interface Foo { String value(); }` | `annotation class Foo(val value: String)` | > | `String value() default "x"` | `val value: String = "x"` | > | retention по умолчанию `CLASS` | по умолчанию `RUNTIME` | > | `@Target(ElementType.FIELD)` | `@Target(AnnotationTarget.FIELD)` | > | нет понятия свойства | `@property:`, `@field:`, `@get:`, `@set:` | > | `@Repeatable(Container.class)` + контейнер | просто `@Repeatable` | > | `Foo.class` в параметре | `Foo::class` | > | `{1, 2}` для массива | `[1, 2]` | > | `getAnnotation(Foo.class)` | `findAnnotation()` | > | аннотации на файл невозможны | `@file:JvmName(...)` | > > **Use-site targets существуют потому, что свойство - не поле.** В Java `@Column private String name` однозначна: цель одна. В Kotlin `val name: String` порождает поле, геттер и Kotlin-свойство как отдельную сущность в метаданных, а в конструкторе ещё и параметр - аннотация должна выбрать одну цель. По умолчанию выбирается первая применимая в порядке `param` → `property` → `field`, и это главная причина, по которой Java-библиотеки «не видят» аннотацию: Jackson читает поле, а аннотация оказалась на параметре. Префикс `@field:` или новый `@all:` решают проблему. Подробнее: [Свойства](../ООП/Свойства.md) > > **Retention по умолчанию - `RUNTIME`, а не `CLASS`.** Java по умолчанию не включает аннотацию в рантайм, поэтому каждая библиотека обязана ставить `@Retention(RUNTIME)` явно. Kotlin выбрал обратный дефолт - аннотации чаще читают рефлексией, чем процессором. Обратная сторона: аннотация для линтера без явного `SOURCE` попадёт и в байт-код, и в рантайм, увеличивая артефакт. > > **Повторяемость не требует класса-контейнера.** В Java `@Repeatable` обязывает объявить отдельную аннотацию-контейнер с массивом - лишний код, который приходится писать для каждой повторяемой аннотации. Kotlin генерирует контейнер сам; `findAnnotations()` возвращает список, и раскрывать контейнер вручную не нужно. > > **Аннотации на файл - новая цель, которой в Java нет.** `@file:JvmName("Utils")` меняет имя класса-контейнера для функций верхнего уровня, `@file:Suppress` подавляет предупреждения во всём файле. В Java единица объявления - всегда класс, поэтому и цели `FILE` быть не может. Подробнее: [Пакеты и импорты](../Синтаксис%20и%20типы/Базовый%20синтаксис/Пакеты%20и%20импорты.md) > > **Генерация кода вместо рефлексии - предпочтительный путь.** В Java-экосистеме аннотации почти всегда читаются рефлексией: Spring, Jackson, Hibernate строят модель при старте приложения. Kotlin подталкивает к другому: `kotlinx.serialization` и KSP обрабатывают аннотации при компиляции и генерируют код, поэтому `kotlin-reflect` не нужен, старт быстрее, а ошибки видны при сборке. Для Kotlin/Native и Kotlin/JS это единственный работающий вариант. Подробнее: [KSP](../Инструментарий/KSP.md) > > Сравнить: [Reflection API в Java](../../Программирование%20на%20Java/Рефлексия/Reflection%20API.md) > [!warning] Подводные камни > **Аннотация без префикса может не попасть туда, куда нужно Java-библиотеке.** Проверено: аннотация с целями `VALUE_PARAMETER` и `PROPERTY` на `val` в конструкторе попадает на параметр и Kotlin-свойство, но **не на поле** - Jackson, Hibernate и JPA её не увидят. Нужен `@field:` или `@all:`. > > **`@property:` видна только Kotlin-рефлексии.** В Java-представлении такой аннотации нет вообще: она хранится в метаданных Kotlin. Библиотека на Java Reflection её не найдёт никогда. > > **Retention по умолчанию `RUNTIME` раздувает байт-код.** Аннотации для KSP, линтеров и внутренних проверок стоит помечать `@Retention(AnnotationRetention.SOURCE)` - иначе они остаются в классах и видны рефлексии без необходимости. > > **`BINARY` не видна рефлексии, хотя лежит в байт-коде.** Проверено: из `@Src @Bin @Run` рефлексия вернёт только `Run`. Это частая причина «аннотация есть в коде, а `findAnnotation` даёт `null`». > > **`null` в качестве значения аннотации невозможен.** Типы параметров не могут быть nullable, поэтому «отсутствие значения» кодируют пустой строкой или сигнальной константой. Это ограничение формата аннотаций в JVM, а не Kotlin. > > **Параметры аннотаций ограничены по типам.** `data class`, коллекции, `Duration` и функции использовать нельзя - только примитивы, `String`, `KClass`, `enum`, другая аннотация и массивы из них. Попытка передать `List` вместо `Array` не скомпилируется. > > **`KClass` в параметре, а не `Class`.** `@Validators([NotBlank::class])` - правильно, `NotBlank::class.java` в аннотации не примут. При чтении обратно получается `KClass`, и для Java-API нужен `.java`. > > **`@JvmStatic` работает только в `companion object` и `object`.** На обычном методе класса даёт ошибку компиляции - статических методов у Kotlin-классов не бывает. Подробнее: [Object и companion object](../Синтаксис%20и%20типы/Базовые%20типы%20ООП/Object%20и%20companion%20object.md) > > **`@JvmOverloads` генерирует перегрузки только справа налево.** Для `f(a, b = 1, c = 2)` появятся `f(a)`, `f(a, b)`, `f(a, b, c)` - вызвать `f(a, c)` из Java не получится. Проверено на реальном байт-коде. > > **`@Throws` обязательна для Java-вызывающих.** Kotlin не имеет проверяемых исключений, поэтому без `@Throws` Java-код не сможет написать `catch (IOException e)` - компилятор Java сообщит, что исключение не бросается. Подробнее: [Система исключений](../Исключения/Система%20исключений.md) > > **Аннотации на типах требуют явной цели `TYPE`.** `List<@Marker String>` не скомпилируется, если в `@Target` нет `AnnotationTarget.TYPE` - по умолчанию эта цель не включена. > > **`@Target(AnnotationTarget.EXPRESSION)` работает только с `SOURCE`.** Выражения не существуют в байт-коде, поэтому `RUNTIME` с такой целью даст ошибку компиляции. > > **`@all:` - новая возможность и ломает сборку на старых версиях Kotlin.** В 2.4 работает без флагов, в более ранних версиях требует `-Xannotation-target-all` или недоступна вовсе. Для библиотек с широкой совместимостью пока надёжнее перечислять цели вручную. > > **Чтение аннотаций рефлексией стоит дорого при первом обращении.** Это тот же разбор метаданных, что и у остальной рефлексии: ~150 мс на класс впервые. Кешируйте результат, а лучше переносите обработку в KSP. Подробнее: [Рефлексия](Рефлексия.md) > > **Обфускация ломает аннотации с именами в строках.** `@Column("user_id")` переживёт R8, а вот поиск свойства по имени из аннотации - нет: само свойство переименуется. Нужны правила `-keep`. Далее: [Kotlin](../Kotlin.md)