Рефлексия и интроспекция в Kotlin: заглядываем под капот JVM

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Рефлексия и интроспекция в Kotlin: как заглянуть внутрь своего кода

Представьте, что вы — программист, который пишет код, который сам себя анализирует. Звучит как научная фантастика? В мире Kotlin это реальность. Рефлексия и интроспекция — это те инструменты, которые превращают ваш код из простого исполнителя в настоящего мета-программиста. Если вы когда-нибудь хотели динамически получать информацию о классах, вызывать методы по имени или работать с аннотациями в рантайме — эта статья для вас.

Мы разберем, как Kotlin, будучи языком, работающим поверх JVM, использует рефлексию, и как с помощью простых примеров вы сможете добавить магии в свои проекты. Поехали!

Что такое рефлексия и интроспекция? Разница, которую нужно знать

Прежде чем нырять в код, давайте разберемся с терминами. Часто эти понятия путают, но между ними есть тонкая грань.

  • Интроспекция — это способность программы "смотреть на себя". Вы можете получить информацию о структуре классов: какие у них свойства, методы, конструкторы. Это как открыть капот машины и посмотреть, что там внутри.
  • Рефлексия — это более мощная штука. Вы не только смотрите, но и меняете поведение программы во время выполнения. Можете вызывать методы, которые не знали на этапе компиляции, создавать объекты по строковому имени класса.

В Kotlin рефлексия тесно связана с JVM-рефлексией, но имеет свои Kotlin-специфичные API (например, KClass, KFunction). И да, для работы с рефлексией вам потребуется добавить зависимость в build.gradle.kts:

dependencies {
    implementation("org.jetbrains.kotlin:kotlin-reflect:1.9.22")
}

Без неё вы сможете использовать только базовые возможности, например, javaClass. Но для полного Kotlin-опыта — ставьте библиотеку.

KClass: ваш главный инструмент интроспекции

В Java мы привыкли к Class<?>. В Kotlin его аналог — KClass. Это точка входа в мир рефлексии. Получить KClass можно двумя способами:

// Способ 1: через объект класса
val myClass: KClass<String> = String::class

// Способ 2: через экземпляр
val str = "Привет, мир!"
val classFromInstance: KClass<out String> = str::class

Теперь у вас есть доступ к куче информации: имя класса, пакет, конструкторы, свойства, функции. Посмотрим на примере:

data class User(val name: String, val age: Int)

fun main() {
    val kClass = User::class
    println("Имя класса: ${kClass.simpleName}") // User
    println("Пакет: ${kClass.qualifiedName}") // com.example.User
    println("Число свойств: ${kClass.memberProperties.size}") // 2 (name, age)
    
    kClass.memberProperties.forEach { prop ->
        println("Свойство: ${prop.name}, тип: ${prop.returnType}")
    }
}

Это чистая интроспекция в Kotlin — мы просто смотрим на структуру. Никакого изменения поведения.

Получаем информацию о функциях и конструкторах

Иногда нужно узнать, какие методы есть у класса, или даже вызвать их. Для этого используем KFunction.

class Calculator {
    fun add(a: Int, b: Int): Int = a + b
    fun multiply(a: Int, b: Int): Int = a * b
}

fun main() {
    val calcClass = Calculator::class
    println("Функции класса Calculator:")
    calcClass.memberFunctions.forEach { func ->
        println(" - ${func.name} (параметры: ${func.parameters})")
    }
}

Вывод будет примерно таким:

Функции класса Calculator:
 - add (параметры: [a, b])
 - multiply (параметры: [a, b])

Рефлексия в действии: вызываем методы и создаем объекты

А теперь самое интересное — рефлексия. Допустим, у вас есть строка с именем метода, и вы хотите его вызвать. Без рефлексии это невозможно, а с ней — пара строк кода.

Вызов метода по имени

fun main() {
    val calculator = Calculator()
    val methodName = "add"
    
    // Находим функцию по имени
    val function = Calculator::class.memberFunctions.first { it.name == methodName }
    
    // Вызываем её с аргументами
    val result = function.call(calculator, 5, 3)
    println("Результат вызова $methodName: $result") // 8
}

Обратите внимание: call принимает первым аргументом экземпляр класса (если функция не статическая), а затем — параметры. Это мощный механизм для создания гибких фреймворков.

Динамическое создание объектов

Хотите создать объект, зная только имя класса? Легко!

fun createInstance(className: String): Any? {
    val kClass = Class.forName(className).kotlin
    // Вызываем первичный конструктор
    return kClass.primaryConstructor?.call("Алиса", 30)
}

fun main() {
    val user = createInstance("com.example.User")
    println(user) // User(name=Алиса, age=30)
}

Важно: primaryConstructor может вернуть null, если у класса нет конструктора без параметров или он не объявлен. Для data-классов это работает отлично.

Аннотации и рефлексия: динамическая настройка поведения

Аннотации — это метки, которые вы вешаете на классы, функции или свойства. А рефлексия позволяет их читать и реагировать на них. Это основа многих фреймворков (например, Retrofit, Dagger).

Создадим простую аннотацию для валидации:

@Target(AnnotationTarget.PROPERTY)
@Retention(AnnotationRetention.RUNTIME)
annotation class MinLength(val min: Int)

data class Form(
    @MinLength(3) val username: String,
    @MinLength(5) val password: String
)

Теперь напишем функцию, которая проверяет, соответствует ли объект правилам:

fun validate(obj: Any): Boolean {
    val kClass = obj::class
    kClass.memberProperties.forEach { prop ->
        val annotation = prop.findAnnotation<MinLength>()
        if (annotation != null) {
            val value = prop.get(obj) as? String ?: continue
            if (value.length < annotation.min) {
                println("Ошибка: ${prop.name} должен быть минимум ${annotation.min} символов")
                return false
            }
        }
    }
    return true
}

fun main() {
    val form = Form("Al", "secret123")
    println(validate(form)) // false, username слишком короткий
}

Это классический пример использования рефлексии Kotlin для валидации данных. Вы отделяете логику проверки от самих данных — код становится чище и гибче.

KFunction и KProperty: тонкая настройка

Kotlin reflection API предлагает

Блоги

Рекомендаций по книгам