Reflection (refleksja) — mechanizm runtime, który pozwala kodowi badać własną strukturę: pobierać klasy, metody, pola i adnotacje bez znajomości typów na etapie kompilacji. To narzędzie leży u podstaw wielu mobilnych frameworków — serializacji JSON (Gson, Moshi), dependency injection (Dagger, Koin) i runnerów testowych (JUnit, XCTest). Według Oracle Java Reflection Tutorial, 2024, reflection jest obowiązkowym elementem platformy Java, używanym przez wszystkie duże biblioteki.
Najważniejsze
Reflection — zdolność programu do obserwowania i modyfikowania własnej struktury oraz zachowania podczas wykonywania. W językach obiektowych oznacza to pobieranie obiektów Class, Method, Field i Constructor, które reprezentują elementy programu jako dane dostępne do odczytu i wywołania.
Termin „reflection” został wprowadzony w środowisku sztucznej inteligencji w 1982 roku (Brian Cantwell Smith) i zaimplementowany w języku Smalltalk. W programowaniu mobilnym reflection po raz pierwszy pojawił się w Java ME i Objective-C (1986, NextStep). Dziś każda duża platforma mobilna ma własne API reflection: Java/Kotlin dla Androida, Objective-C Runtime dla iOS, Swift Mirror API dla Swifta.
Mechanizm reflection opiera się na metadanych zapisywanych przez kompilator w bajtkodzie lub pliku binarnym. Android przechowuje pełne informacje o klasach w plikach DEX, iOS — w segmencie __objc_classlist w Mach-O. Runtime ładuje te metadane do pamięci i udostępnia API do ich przeglądania.
Java Reflection API buduje się wokół klasy java.lang.Class. Dowolny obiekt w Javie można przekształcić w Class przez .getClass() lub Class.forName(). Z Class pobierane są wszystkie metody, pola, konstruktory, adnotacje i superklasy. Kotlin dziedziczy reflection Javy i dodaje własne KClass, KFunction, KProperty z pakietu kotlin.reflect.
import kotlin.reflect.full.declaredMemberFunctions
data class User(
val name: String,
val email: String
)
fun inspectClass() {
val kClass = User::class
val properties = kClass.declaredMemberProperties
val functions = kClass.declaredMemberFunctions
properties.forEach { prop ->
println("Właściwość: ${prop.name}, typ: ${prop.returnType}")
}
}
W tym przykładzie KClass udostępnia metadane data class User. declaredMemberProperties zwraca listę właściwości wraz z ich typami i getterami. Reflection w Kotlin jest ściśle zintegrowane z korutynami: KFunction obsługuje modyfikator suspend, co pozwala wywoływać metody asynchroniczne przez reflection.
Reflection w Javie działa z Class<?>, Method.setAccessible() i Field.get(). setAccessible(true) wyłącza sprawdzanie dostępu Java language access control dla elementów private. To potężny, ale niebezpieczny mechanizm: na Androidzie od API 28 wywołanie setAccessible na ukrytych metodach systemowych może spowodować InaccessibleObjectException.
// Reflection w Javie: wywołanie prywatnej metody
Class> clazz = Class.forName("com.example.MyClass");
Object instance = clazz.getDeclaredConstructor().newInstance();
Method method = clazz.getDeclaredMethod("privateMethod", String.class);
method.setAccessible(true);
method.invoke(instance, "reflection test");
Kod demonstruje Class.forName() — dynamiczne ładowanie klasy po nazwie w formie stringa. To podstawa architektur wtyczkowych: klasa może być nieznana na etapie kompilacji, ale załadowana i wykonana przez reflection w runtime. getDeclaredMethod(„privateMethod”, ...) znajduje metodę po nazwie i typach parametrów, a invoke ją wykonuje.
Objective-C runtime udostępnia funkcje class_copyMethodList, class_copyPropertyList, objc_getAssociatedObject. W przeciwieństwie do Javy, Objective-C nie ukrywa domyślnie metod prywatnych — runtime widzi wszystkie metody klasy. To wyjaśnia, dlaczego method swizzling działa bez setAccessible: runtime nie ma enkapsulacji na poziomie metadanych.
Reflection jest używane w kluczowych bibliotekach programowania mobilnego. Serializacja JSON (Gson, Moshi, Kotlinx.serialization) pobiera właściwości obiektu przez reflection i dopasowuje je do kluczy JSON. Dependency injection (Dagger, Koin, Swinject) analizuje konstruktory i pola w celu automatycznego wstrzykiwania zależności. Biblioteki ORM (Room, Realm) używają reflection do mapowania klas na tabele bazy danych.
Każde z tych zastosowań działa właśnie w runtime — kod nie wie z góry, z jakimi klasami się zderzy. Reflection zapewnia uniwersalny mechanizm radzenia sobie z tą niewiadomą kosztem wydajności i bezpieczeństwa.
Reflection jest 10–100 razy wolniejsze niż bezpośrednie wywoływanie metod. Przyczyną jest brak optymalizacji JIT (devirtualization, inlining), sprawdzanie typów przy każdym wywołaniu oraz pakowanie parametrów w Object[]/varargs. ART na Androidzie 14 nie może optymalizować wywołań reflection przez inline, ponieważ docelowa metoda jest nieznana do momentu wykonania.
| Operacja | Bezpośrednie wywołanie | Przez Reflection | Spowolnienie |
|---|---|---|---|
| Wywołanie metody bez parametrów | ~3 ns | ~120 ns | 40x |
| Odczyt pola int | ~1 ns | ~85 ns | 85x |
| Wywołanie metody z 2 parametrami | ~4 ns | ~250 ns | 62x |
| Utworzenie instancji przez konstruktor | ~5 ns | ~180 ns | 36x |
| Określenie klasy po nazwie stringa | — | ~800 ns | — |
Dane uzyskano na Google Pixel 8 (Android 14, ART). Wydajność reflection poprawia się z każdą wersją Androida: na Androidzie 9 wywołanie przez Method.invoke() było 150 razy wolniejsze niż bezpośrednie, na Androidzie 14 — 40 razy. ART używa wbudowanych mechanizmów method handle do optymalizacji.
W krytycznych pod względem wydajności miejscach programiści zastępują reflection kodogeneracją: Dagger używa annotation processing zamiast wyszukiwania w runtime, Kotlinx.serialization generuje serializatory przez KSP, Moshi dostosowuje @JsonClass(generateAdapter = true) do codegen na etapie kompilacji.
Annotation processing (KAPT, KSP) i code generation — główne alternatywy dla reflection w programowaniu mobilnym. Przenoszą analizę metadanych z runtime na compile time: kod jest generowany przed uruchomieniem aplikacji, co eliminuje narzut reflection i poprawia wydajność.
// KSP: code generation zamiast reflection
@Serializable
data class Config(
val apiUrl: String,
val timeout: Int
)
// KSP generuje ConfigSerializer bez reflection
fun loadConfig(json: String): Config {
return Config.serializer().decodeFromString(json)
}
W tym przykładzie @Serializable to adnotacja Kotlinx.serialization. KSP (Kotlin Symbol Processing) analizuje kod źródłowy na etapie kompilacji, znajduje wszystkie klasy @Serializable i generuje serializatory. Podczas działania aplikacji reflection nie jest używane — serializator jest już skompilowany do kodu maszynowego.
Code generation zapewnia lepszą wydajność, bezpieczeństwo typów i mniejszy rozmiar pliku binarnego (dead code elimination usuwa nieużywane zależności reflection). Reflection pozostaje niezbędne do zadań, w których typy są nieznane na etapie kompilacji: dynamiczne ładowanie wtyczek, runtime-proxy, instrumentacja testów. Według Kotlin, Kotlinx.serialization z KSP jest 3–5 razy szybszy niż Gson oparty na reflection.
Reflection na platformach mobilnych ma ograniczenia bezpieczeństwa i wydajności. Android od API 28 (Pie) ogranicza setAccessible dla interfejsów non-SDK — próba otwarcia ukrytej metody systemowej prowadzi do wyjątku lub ostrzeżenia. iOS ze Swift nie obsługuje reflection w klasycznym rozumieniu: Swift Mirror API udostępnia tylko odczyt właściwości (name, value) bez modyfikacji lub wywoływania metod.
Google Play odrzuca aplikacje używające reflection do obchodzenia ograniczeń platformy: podmiany usług systemowych, modyfikacji polityk SELinux, odczytu chronionych uprawnień. Apple również blokuje aplikacje wywołujące prywatne API przez reflection — kontrola App Review skanuje plik binarny pod kątem sygnatur stringów objc_msgSend ze znanymi prywatnymi selektorami.
ProGuard/R8 — kolejne ograniczenie. Obfuskacja i minifikacja kodu zmieniają nazwy klas i metod na krótkie (a, b, c). Jeśli kod używa Class.forName(„com.example.MyClass”), ulegnie zniszczeniu po obfuskacji. Rozwiązaniem są reguły keep w proguard-rules.pro:
// Reguły keep ProGuard dla reflection
-keep class com.example.** { *; }
-keep class * implements java.io.Serializable { *; }
-keepclassmembers class * {
@com.google.gson.annotations.SerializedName ;
}
-keepattributes Signature, InnerClasses, EnclosingMethod
Reguły -keep informują R8, aby nie zmieniał nazw klas używanych przez reflection. Bez tych reguł zobfuskowana aplikacja upadnie z ClassNotFoundException — runtime nie będzie w stanie znaleźć klasy po nazwie stringa, która się zmieniła.
Często zadawane pytania
Tak, reflection jest 10–100 razy wolniejsze niż bezpośrednie wywołanie. Główne przyczyny: brak optymalizacji JIT (inlining, devirtualization), pakowanie parametrów i sprawdzanie typów przy każdym wywołaniu. W kodzie produkcyjnym zaleca się zastępowanie reflection code generation przez KSP lub annotation processing.
Reflection w Javie działa przez Class, Method, Field i wymaga setAccessible dla elementów private. Reflection w Kotlin używa KClass, KFunction, KProperty i obsługuje sealed class, data class, korutyny (funkcje suspend) oraz null-safety. Reflection w Kotlin opiera się na reflection w Javie, ale dodaje type-safe API.
Dodać reguły keep ProGuard/R8 dla klas, metod i pól używanych przez reflection. Dla każdego Class.forName(), getDeclaredMethod(), getDeclaredField() musi istnieć odpowiednia dyrektywa -keep. Narzędzia takie jak GreenDAO i Room automatycznie generują reguły keep.
Swift nie ma reflection w pełnym znaczeniu. Mirror API (Swift 2+) pozwala odczytywać właściwości struktury lub klasy: nazwę, wartość, typ. Wywoływanie metod, modyfikacja pól i tworzenie instancji po typie nie są możliwe. Do tego służy Objective-C Runtime przy dziedziczeniu z NSObject z @objc dynamic.
Gson (serializacja JSON), Retrofit (tworzenie implementacji interfejsów przez dynamic proxy), Mockito (tworzenie mocków), Koin (dependency injection), Room (sprawdzanie Entity na etapie kompilacji przez KAPT), Firebase Crashlytics (analiza stack trace). Większość bibliotek przechodzi na code generation z KSP/KAPT.
Podsumowanie
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.
Przeczytaj również