Reified w Kotlin — co to jest, składnia i zastosowanie

Autor: IT Sectr Opublikowano: 2026-06-21 Czas czytania: 8 min

reified — słowo kluczowe w języku Kotlin, które pozwala uzyskać dostęp do typu parametru generic wewnątrz inline-funkcji w czasie wykonania. W zwykłych generics działa type erasure — informacja o typie jest usuwana na etapie kompilacji, ale reified ją zachowuje. Według Kotlin Documentation, 2025, reified działa tylko wewnątrz inline-funkcji, ponieważ kompilator podstawia rzeczywisty typ na etapie wstawiania.

Najważniejsze

  • reified — modyfikator parametru generic, zachowujący informację o typie w runtime
  • Inline only — reified działa wyłącznie wewnątrz inline-funkcji
  • Type erasure — standardowy mechanizm Java/Kotlin, usuwający typy generic; reified go omija
  • Sprawdzanie is — możliwe: if (value is T) zamiast if (value is String)
  • Tworzenie instancji — tworzenie T::class.java.newInstance() bez przekazywania Class

Czym jest reified w Kotlin?

reified — to modyfikator parametru generic inline-funkcji, który czyni typ rzeczywistym (reify — «uprzytamniać») w runtime. Bez reified typ T wewnątrz funkcji generic jest niedostępny — kompilator stosuje type erasure, usuwając całą informację o typie. reified zmusza kompilator do podstawienia konkretnego typu w miejscu wywołania, czyniąc go dostępnym przez T::class i operator is.

Według Kotlin Survey by Kodee (2024), reified type parameters wchodzą do dziesiątki najbardziej pożądanych funkcji Kotlin — używa ich 52% ankietowanych programistów, głównie do pisania generic-fabryk, kontenerów DI i serializatorów. Szczególnie popularny jest reified w połączeniu z Gson, Moshi i Kotlinx Serialization.

Technicznie mechanizm jest prosty: przy wywołaniu inline-funkcji z reified parametrem kompilator zna konkretny typ argumentu (Int, String, User) i podstawia go zamiast T. W kodzie bajtowym reified-parametr zamienia się w zwykły Class, przekazywany jako ukryty argument.

Używaj reified do pisania funkcji generic, gdzie wymagany jest typ w runtime — tworzenie instancji, sprawdzanie typu, uzyskiwanie Class dla refleksji lub serializacji.

Problem type erasure w generics

Type erasure — mechanizm Java i Kotlin, w którym informacja o parametrach generic jest usuwana podczas kompilacji. W kodzie bajtowym List i List stają się po prostu List. Zostało to zrobione dla zachowania wstecznej kompatybilności z Java 1.4, gdzie nie było generics, ale stwarza ograniczenia przy pracy z typami w runtime.

kotlin
// ❌ Error: Cannot check for instance of erased type
fun <T> checkType(value: Any) {
    if (value is T) { // type erasure — T is unknown
        println("Type matches")
    }
}

// ✅ Solution: pass Class as parameter
fun <T> checkTypeWithClass(
    value: Any,
    clazz: Class<T>
) {
    if (clazz.isInstance(value)) {
        println("Type matches")
    }
}

W przykładzie checkType nie kompiluje się z powodu type erasure — kompilator nie wie, jaki typ podstawić zamiast T. W checkTypeWithClass problem został rozwiązany przez jawne przekazanie Class, ale wymaga to boilerplate: każde wywołanie wymaga .java lub ::class.java. reified eliminuje ten boilerplate całkowicie.

Składnia reified i mechanizm działania

Modyfikator reified umieszcza się przed parametrem generic w funkcji inline. Funkcja musi być koniecznie inline — kompilator musi mieć możliwość podstawienia konkretnego typu na etapie wstawiania.

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

fun main() {
    println(isA<String>("Hello")) // true
    println(isA<Int>("Hello"))  // false
}

Podczas kompilacji wywołanie isA("Hello") zamieniane jest na sprawdzenie value is String. Wywołanie isA("Hello") — na value is Int. Typ jest podstawiamy dosłownie, co daje możliwość używania is, as, ::class i innych operacji niedostępnych przy type erasure.

Dekompilacja funkcji reified

Jeśli zdekompilować kod bajtowy isA("Hello"), IntelliJ IDEA pokaże mniej więcej taki wynik w Javie: String.class.isInstance(value). Zamiast parametru generic kompilator podstawił konkretny java.lang.String.class — żadnej refleksji z wyszukiwaniem typu po nazwie, tylko bezpośrednie odwołanie do klasy.

Sprawdzanie typów z reified: is i as

Najczęstsze zastosowanie reified — sprawdzanie typu przez operator is. W zwykłej funkcji generic value is T nie kompiluje się. Z reified działa to jak ze zwykłą klasą: value is String, value is List (prawie — z uwzględnieniem ograniczeń reified dla typów sparametryzowanych).

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]

Funkcja rozszerzająca filterByType filtruje listę, pozostawiając tylko elementy określonego typu. Bez reified trzeba by pisać filterByType(list) z parametrem Class. Z reified wywołanie czyta się jako naturalną operację na liście, co poprawia czytelność łańcuchów przetwarzania danych.

Według danych Kotlin Coroutines Guide (JetBrains, 2025), reified type checks są używane w launch i async do przekazywania typu wyniku coroutine, co pozwala uniknąć jawnego określania typu w większości przypadków.

Refleksja z reified: tworzenie instancji i dostęp do Class

reified daje dostęp do T::class — referencji do KClass, z której można uzyskać Java Class przez .java. Otwiera to możliwości tworzenia instancji przez refleksję, pracy z serializatorami i uzyskiwania adnotacji klasy podczas wykonania.

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

// Usage
data class User(val name: String = "default")
val user = createInstance<User>()

// Serialization with Gson
inline fun <reified T> Gson.fromJson(json: String): T =
    this.fromJson(json, T::class.java)

// Getting annotations
inline fun <reified T> hasAnnotation<A>(): Boolean where A : Annotation =
    T::class.java.isAnnotationPresent(A::class.java)

Opakowanie fromJson dla Gson — klasyczny przykład użycia reified w produkcji. Zamiast gson.fromJson(json, User::class.java) można pisać gson.fromJson(json). Wydaje się to niewielką poprawą, ale w projekcie z setkami wywołań serializacji reified znacząco redukuje boilerplate i czyni kod czystszym.

Ograniczenia reified i alternatywy

reified ma ograniczenia. Po pierwsze — działa tylko wewnątrz funkcji inline. Jeśli funkcji nie można uczynić inline (na przykład jest rekurencyjna lub zbyt duża), reified jest niedostępne. Po drugie — reified nie może być używane z suspend-funkcjami bezpośrednio, tylko przez opakowania inline.

Po trzecie — reified nie działa w pełni z typami sparametryzowanymi. Na przykład filterByType>() może dać nieoczekiwany wynik, ponieważ dla typów sparametryzowanych reified zachowuje tylko surowy typ (List), bez określenia argumentów generic. Do pełnego sprawdzenia typów sparametryzowanych wymagana jest refleksja z TypeToken.

OperacjaZ reifiedBez reified
value is T✅ Działa❌ Błąd kompilacji
T::class✅ Działa❌ Błąd kompilacji
List is T⚠️ Tylko raw type❌ Błąd
Tworzenie instancji✅ Przez refleksję❌ Wymagany Class
Suspend-funkcja❌ Tylko przez opakowanie inline❌ Nie dotyczy

W przypadkach, gdy reified jest niedostępne, używaj wzorca z jawnym przekazaniem Class lub TypeToken z bibliotek (na przykład Gson TypeToken lub Jackson TypeReference). To podejście działa w dowolnych funkcjach, ale wymaga boilerplate i jest mniej wygodne.

Często zadawane pytania

Dlaczego reified działa tylko z funkcjami inline?

Kompilator zamienia reified-parametr T na konkretny typ podczas wstawiania ciała funkcji. Jeśli funkcja nie jest inline, kompilator nie ma miejsca, gdzie podstawić typ — wywołanie funkcji generic odbywa się przez jednolity kod bajtowy, w którym T jest wymazany. Inline tworzy osobną kopię kodu bajtowego dla każdego typu-argumentu.

Czy można zadeklarować reified property?

Nie, reified ma zastosowanie tylko do parametrów funkcji. Dla właściwości używa się wzorca inline fun z zwracaniem wartości lub jawnego przekazania Class przez konstruktor. Extension properties również nie obsługują reified.

Jak reified działa z typami nullable?

reified obsługuje typy nullable: reified T : Any (non-null) i po prostu reified T (może być nullable). Dla typów nullable T::class zwraca klasę dla wersji non-null (String::class dla String?). Sprawdzenie value is T uwzględnia null: jeśli T = String?, to null is T = true.

Czy reified ma narzut wydajnościowy?

Minimalny. reified nie używa refleksji — kompilator podstawia konkretny typ na etapie wstawiania. W kodzie bajtowym jest to bezpośrednie odwołanie do klasy (ldc + checkcast/invokevirtual). Narzut nie występuje w porównaniu z ręcznym przekazaniem Class — oba warianty generują identyczny kod bajtowy.

Czy można używać reified w Androidzie?

Tak, reified jest aktywnie używane w Androidzie. Bundle.getParcelable(), Intent.getSerializableExtra(), viewModels() z Android KTX — wszystkie te funkcje używają reified, aby uniknąć jawnego przekazywania Class. Według danych Google Android Docs (2025), reified jest zalecane dla API generic, gdzie wymagany jest typ w runtime.

Podsumowanie

  • reified — modyfikator parametru generic inline-funkcji, zachowujący typ w runtime
  • Type erasure — standardowy mechanizm usuwania typów; reified omija go przez wstawianie
  • is/as — sprawdzanie i rzutowanie typów działa z reified jak ze zwykłymi klasami
  • Referencja do klasy — T::class i T::class.java są dostępne dla refleksji i serializacji
  • Tylko inline — reified jest niemożliwe bez funkcji inline z powodu mechanizmu podstawiania typu
  • Typy sparametryzowane — reified nie zachowuje argumentów generic (tylko raw type)
  • Zastosowanie — serializacja Gson/Moshi, kontenery DI, sprawdzanie typów w kolekcjach, Android KTX

Opracujemy aplikację mobilną pod klucz

IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.

Omów projekt

Przeczytaj również