Reified в Kotlin — що це, синтаксис та застосування

Автор: IT Sectr Опубліковано: 2026-06-21 Час читання: 8 хв

reified — це ключове слово в Kotlin, яке дозволяє отримати доступ до типу дженеричного параметра всередині інлайн-функцій під час виконання. У звичайних дженериках діє стирання типів (type erasure) — інформація про тип стирається на етапі компіляції, але reified зберігає її. Згідно з Kotlin Documentation, 2025, reified працює тільки всередині inline-функцій, оскільки компілятор підставляє реальний тип на етапі вбудовування.

Головне

  • reified — модифікатор дженеричного параметра, який зберігає інформацію про тип під час виконання
  • Тільки inline — reified працює виключно всередині inline-функцій
  • Стирання типів — стандартний механізм Java/Kotlin, який стирає дженеричні типи; reified обходить його
  • Перевірки is — можливі: if (value is T) замість if (value is String)
  • Створення екземплярів — T::class.java.newInstance() без передачі Class<T>

Що таке reified в Kotlin?

reified — це модифікатор дженеричного параметра inline-функції, який робить тип реальним (reify — «уречевити») під час виконання. Без reified тип T всередині дженеричної функції недоступний — компілятор застосовує стирання типів, видаляючи всю інформацію про тип. reified змушує компілятор підставити конкретний тип в місці виклику, роблячи його доступним через T::class та оператор is.

За даними Опитування Kotlin від Kodee (2024), параметри типу reified входять до десятка найбільш запитуваних фіч Kotlin — їх використовують 52% опитаних розробників, насамперед для написання дженеричних фабрик, DI-контейнерів та серіалізаторів. Особливо популярний reified у поєднанні з Gson, Moshi та Kotlinx Serialization.

Технічно механізм простий: при виклику inline-функції з reified-параметром компілятор знає конкретний тип аргументу (Int, String, User) і підставляє його замість T. У байт-коді reified-параметр перетворюється на звичайний Class<T>, що передається як прихований аргумент.

Використовуйте reified для написання дженеричних функцій, де потрібен тип під час виконання — створення екземплярів, перевірки типів, отримання Class<T> для рефлексії або серіалізації.

Проблема стирання типів в дженериках

Стирання типів — це механізм Java та Kotlin, при якому інформація про дженеричні параметри стирається під час компіляції. У байт-коді List<String> та List<Int> стають просто List. Це було зроблено для зворотньої сумісності з Java 1.4, де не було дженериків, але це створює обмеження при роботі з типами під час виконання.

kotlin
// ❌ Помилка: Неможливо перевірити екземпляр стертого типу
fun <T> checkType(value: Any) {
    if (value is T) { // стирання типів — T невідомий
        println("Тип збігається")
    }
}

// ✅ Solution: pass Class as parameter
fun <T> checkTypeWithClass(
    value: Any,
    clazz: Class<T>
) {
    if (clazz.isInstance(value)) {
        println("Тип збігається")
    }
}

У прикладі checkType не компілюється через стирання типів — компілятор не знає, який тип підставити замість T. У checkTypeWithClass проблему вирішено явною передачею Class<T>, але це вимагає boilerplate: кожен виклик супроводжується .java або ::class.java. reified повністю усуває цей boilerplate.

Синтаксис reified та механізм роботи

Модифікатор reified ставиться перед дженеричним параметром в inline-функції. Функція обов’язково повинна бути inline — компілятор має бути здатним підставити конкретний тип на етапі вбудовування.

kotlin
inline fun <reified T> isA(value: Any): Boolean {
    return value is T
}

fun main() {
    println(isA<String>("Привіт")) // true
    println(isA<Int>("Привіт"))  // false
}

Під час компіляції виклик isA<String>(«Hello») замінюється на перевірку value is String. Виклик isA<Int>(«Hello») стає value is Int. Тип підставляється буквально, що дає можливість використовувати is, as, ::class та інші операції, недоступні при стиранні типів.

Декомпіляція reified-функції

Якщо декомпілювати байт-код isA<String>(«Hello»), IntelliJ IDEA покаже приблизно такий результат на Java: String.class.isInstance(value). Замість дженеричного параметра компілятор підставив конкретний java.lang.String.class — жодної рефлексії з пошуком типу за іменем, лише пряме посилання на клас.

Перевірки типів з reified: is та as

Найпоширеніше застосування reified — перевірка типів через оператор is. У звичайній дженеричній функції value is T не компілюється. З reified це працює як зі звичайним класом: value is String, value is List<Int> (майже — з обмеженнями для параметризованих типів).

kotlin
inline fun <reified T> List<Any>.filterByType(): List<T> {
    return this.filter { it is T }.map { it as T }
}

val mixed = listOf("a", 1, "b", 2)
val strings = mixed.filterByType<String>() // ["a", "b"]
val ints = mixed.filterByType<Int>()    // [1, 2]

Функція розширення filterByType фільтрує список, залишаючи лише елементи зазначеного типу. Без reified довелося б писати filterByType<String>(list) з параметром Class<String>. З reified виклик читається як природна операція над списком, що покращує читання ланцюжків обробки даних.

Згідно з Посібником з Kotlin Coroutines (JetBrains, 2025), перевірки типів reified використовуються в launch та async для передачі типу результату корутини, що дозволяє уникнути явного зазначення типу в більшості випадків.

Рефлексія з reified: створення екземплярів та доступ до Class

reified надає доступ до T::class — посилання на KClass, з якого можна отримати Java Class через .java. Це відкриває можливості для створення екземплярів через рефлексію, роботи з серіалізаторами та отримання анотацій класу під час виконання.

kotlin
inline fun <reified T> createInstance(): T =
    T::class.java.getDeclaredConstructor().newInstance()

// Використання
data class User(val name: String = "default")
val user = createInstance<User>()

// Серіалізація з Gson
inline fun <reified T> Gson.fromJson(json: String): T =
    this.fromJson(json, T::class.java)

// Отримання анотацій
inline fun <reified T> hasAnnotation<A>(): Boolean where A : Annotation =
    T::class.java.isAnnotationPresent(A::class.java)

Обортка fromJson для Gson — класичний приклад використання reified у продакшні. Замість gson.fromJson(json, User::class.java) можна писати gson.fromJson<User>(json). Це може здаватися невеликим покращенням, але в проекті з сотнями викликів серіалізації reified значно скорочує boilerplate і робить код чистішим.

Обмеження reified та альтернативи

reified має обмеження. Перше — працює тільки всередині inline-функцій. Якщо функцію неможна зробити inline (наприклад, вона рекурсивна або занадто велика), reified недоступний. Друге — reified не можна використовувати безпосередньо з suspend-функціями, лише через inline-обортки.

Третє — reified не працює повністю з параметризованими типами. Наприклад, filterByType<List<String>>() може дати неочікуваний результат, оскільки для параметризованих типів reified зберігає лише сирий тип (List), без зазначення generic-аргументів. Для повної перевірки параметризованих типів потрібна рефлексія з TypeToken.

ОпераціяЗ reifiedБез reified
value is T✅ Працює❌ Помилка компіляції
T::class✅ Працює❌ Помилка компіляції
List<String> is T⚠️ Лише сирий тип❌ Помилка
Створення екземпляра✅ Через рефлексію❌ Потрібний Class<T>
Suspend-функція❌ Лише через inline обортку❌ Не застосовно

Для випадків, коли reified недоступний, використовуйте патерн з явною передачею Class<T> або TypeToken з бібліотек (наприклад, Gson TypeToken або Jackson TypeReference). Цей підхід працює в будь-якій функції, але вимагає boilerplate і менш зручний.

Часті запитання

Чому reified працює тільки з inline-функціями?

Компілятор замінює reified-параметр T на конкретний тип під час вбудовування тіла функції. Якщо функція не inline, у компілятора немає місця для підстановки типу — виклик дженеричної функції відбувається через єдиний байт-код, де T стертий. Inline створює окрему копію байт-коду для кожного типу-аргументу.

Чи можна оголосити reified властивість?

Ні, reified застосовується лише до параметрів функцій. Для властивостей використовуйте патерн inline fun <reified T> з поверненням значення, або явну передачу Class<T> через конструктор. Розширення також не підтримують reified.

Як reified працює з nullable-типами?

reified підтримує nullable-типи: reified T : Any (не-null) і просто reified T (може бути nullable). Для nullable-типів T::class повертає клас для не-null версії (String::class для String?). Перевірка value is T враховує null: якщо T = String?, то null is T = true.

Чи є накладні витрати у reified?

Мінімальні. reified не використовує рефлексію — компілятор підставляє конкретний тип на етапі вбудовування. У байт-коді це пряме посилання на клас (ldc + checkcast/invokevirtual). Накладні витрати відсутні порівняно з ручною передачею Class<T> — обидва підходи генерують однаковий байт-код.

Чи можна використовувати reified в Android-розробці?

Так, reified активно використовується в Android. Bundle.getParcelable<T>(), Intent.getSerializableExtra<T>(), viewModels<T>() з Android KTX — всі ці функції використовують reified, щоб уникнути явної передачі Class<T>. Згідно з Документацією Android від Google (2025), reified рекомендується для generic-API, де потрібен тип під час виконання.

Підсумки

  • reified — модифікатор дженеричного параметра inline-функції, що зберігає тип під час виконання
  • Стирання типів — стандартний механізм стирання типів; reified обходить його через вбудовування
  • is/as — перевірки та приведення типів працюють з reified як зі звичайними класами
  • Посилання на клас — T::class та T::class.java доступні для рефлексії та серіалізації
  • Тільки inline — reified неможливий без inline-функції через механізм підстановки типу
  • Параметризовані типи — reified не зберігає generic-аргументи (лише сирий тип)
  • Застосування — серіалізація Gson/Moshi, DI-контейнери, перевірки типів у колекціях, Android KTX

Ми розробимо мобільний застосунок під ключ

IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

Читайте також