Reflection (reflexió) — egy futásidőbeli mechanizmus, amely lehetővé teszi a kód számára saját szerkezetének vizsgálatát: osztályok, metódusok, mezők és annotációk lekérését a típusok fordításkori ismerete nélkül. Ez az eszköz számos mobil keretrendszer alapját képezi — a JSON-szerializációét (Gson, Moshi), a dependency injectionét (Dagger, Koin) és a tesztrunnereké (JUnit, XCTest). A Oracle Java Reflection Tutorial, 2024 szerint a reflection a Java platform kötelező eleme, amelyet minden nagyobb könyvtár használ.
A lényeg
Reflection — a program azon képessége, hogy futás közben megfigyelje és módosítsa saját szerkezetét és viselkedését. Az objektumorientált nyelvekben ez azt jelenti, hogy lekérjük a Class, Method, Field és Constructor objektumokat, amelyek a program elemeit olvasható és hívható adatként reprezentálják.
A „reflection” kifejezést 1982-ben vezették be a mesterséges intelligencia közösségében (Brian Cantwell Smith), és a Smalltalk nyelvben valósították meg. A mobilfejlesztésben a reflection először a Java ME-ben és az Objective-C-ben jelent meg (1986, NextStep). Ma minden nagyobb mobilplatformnak saját reflection API-ja van: Java/Kotlin az Androidhoz, Objective-C Runtime az iOS-hez, Swift Mirror API a Swift-hez.
A reflection mechanizmusa a fordítási kódban (bytecode) vagy a bináris fájlban a fordító által tárolt metaadatokon alapul. Az Android a DEX-fájlokban tárolja a teljes osztályinformációt, az iOS pedig a Mach-O __objc_classlist szegmensében. A Runtime betölti ezeket a metaadatokat a memóriába, és API-t biztosít a bejárásukhoz.
A Java Reflection API a java.lang.Class osztály köré épül. Bármely Java objektum Class-szá alakítható a .getClass() vagy a Class.forName() segítségével. A Class-ból minden metódus, mező, konstruktor, annotáció és szuperosztály kinyerhető. A Kotlin örökli a Java reflection-t, és hozzáadja saját KClass, KFunction, KProperty elemeit a kotlin.reflect csomagból.
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("Tulajdonság: ${prop.name}, típus: ${prop.returnType}")
}
}
Ebben a példában a KClass biztosítja a data class User metaadatait. A declaredMemberProperties visszaadja a tulajdonságok listáját a típusaikkal és getterjeikkel együtt. A Kotlin reflection szorosan integrált a korutinokkal: a KFunction támogatja a suspend-modifikátort, ami lehetővé teszi aszinkron metódusok reflectionön keresztüli hívását.
A Java reflection a Class<?>, Method.setAccessible() és Field.get() elemekkel dolgozik. A setAccessible(true) kikapcsolja a Java language access control hozzáférés-ellenőrzést a private elemeknél. Ez egy erőteljes, de veszélyes mechanizmus: Android rendszeren az API 28-tól kezdve a rejtett rendszermetódusokon végzett setAccessible hívás InaccessibleObjectException-t okozhat.
// Java reflection: privát metódus meghívása
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");
A kód a Class.forName() műveletet mutatja be — az osztály szöveges név alapján történő dinamikus betöltését. Ez a plugin-architektúrák alapja: az osztály a fordítási fázisban ismeretlen lehet, de a reflection segítségével futásidőben betölthető és végrehajtható. A getDeclaredMethod(„privateMethod”, ...) név és paramétertípusok alapján találja meg a metódust, az invoke pedig végrehajtja azt.
Az Objective-C runtime a class_copyMethodList, class_copyPropertyList, objc_getAssociatedObject függvényeket biztosítja. A Java-val ellentétben az Objective-C alapértelmezés szerint nem rejti el a privát metódusokat — a runtime látja az osztály összes metódusát. Ez megmagyarázza, miért működik a method swizzling setAccessible nélkül: a runtime metaadatszinten nem rendelkezik enkapszulációval.
A Reflection-t a mobilfejlesztés kulcsfontosságú könyvtárai használják. A JSON-szerializáció (Gson, Moshi, Kotlinx.serialization) a reflection segítségével kéri le az objektum tulajdonságait, és illeszti azokat a JSON-kulcsokhoz. A dependency injection (Dagger, Koin, Swinject) elemzi a konstruktorokat és mezőket a függőségek automatikus befecskendezéséhez. Az ORM-könyvtárak (Room, Realm) a reflectiont az osztályok adatbázistáblákra történő leképezéséhez használják.
Ezen alkalmazások mindegyike pontosan futásidőben működik — a kód nem tudja előre, milyen osztályokkal találkozik. A Reflection univerzális mechanizmust biztosít ennek az ismeretlenségnek a legyőzésére a teljesítmény és a biztonság árán.
A Reflection 10–100-szor lassabb, mint a metódusok közvetlen meghívása. Ennek oka — a JIT-optimalizálások (devirtualization, inlining) hiánya, a típusok ellenőrzése minden hívásnál, valamint a paraméterek Object[]/varargs csomagolása. Az Android 14 rendszeren a ART nem tudja inline-optimalizálni a reflection-hívásokat, mert a célmetódus csak a végrehajtás pillanatában ismert.
| Művelet | Közvetlen hívás | Reflection segítségével | Lassulás |
|---|---|---|---|
| Metódushívás paraméterek nélkül | ~3 ns | ~120 ns | 40x |
| int mező olvasása | ~1 ns | ~85 ns | 85x |
| Metódushívás 2 paraméterrel | ~4 ns | ~250 ns | 62x |
| Példány létrehozása konstruktoron át | ~5 ns | ~180 ns | 36x |
| Osztály meghatározása szöveg alapján | — | ~800 ns | — |
Az adatok Google Pixel 8 (Android 14, ART) készüléken készültek. A reflection teljesítménye minden Android-verzióval javul: Android 9 rendszeren a Method.invoke() hívás 150-szer, Android 14 rendszeren 40-szer volt lassabb a közvetlennél. Az ART a method handle beépített mechanizmusait használja az optimalizáláshoz.
Teljesítménykritikus részeknél a fejlesztők a reflectiont kódgenerálással helyettesítik: a Dagger a futásidőbeli keresés helyett annotation processinget használ, a Kotlinx.serialization a KSP révén generál szerializátorokat, a Moshi a compile-time codegen érdekében a @JsonClass(generateAdapter = true) beállítást alkalmazza.
Az annotation processing (KAPT, KSP) és a code generation — a reflection fő alternatívái a mobilfejlesztésben. A metaadat-elemzést a futásidőről a fordítási időre helyezik át: a kód az alkalmazás indítása előtt generálódik, ami megszünteti a reflection overheadet és javítja a teljesítményt.
// KSP: code generation a reflection helyett
@Serializable
data class Config(
val apiUrl: String,
val timeout: Int
)
// A KSP reflection nélkül generálja a ConfigSerializer-t
fun loadConfig(json: String): Config {
return Config.serializer().decodeFromString(json)
}
Ebben a példában a @Serializable a Kotlinx.serialization annotációja. A KSP (Kotlin Symbol Processing) a fordítási fázisban elemzi a forráskódot, megtalálja az összes @Serializable osztályt, és szerializátorokat generál. Az alkalmazás futása során a reflection nem kerül használatra — a szerializátor már gépi kódra van fordítva.
A code generation jobb teljesítményt, típusbiztonságot és kisebb bináris fájlt biztosít (a dead code elimination eltávolítja a nem használt reflection-függőségeket). A reflection olyan feladatokhoz marad szükséges, ahol a típusok fordítási időben ismeretlenek: pluginok dinamikus betöltése, futásidőbeli proxy, tesztek instrumentálása. A Kotlin adatai szerint a KSP-vel működő Kotlinx.serialization 3–5-ször gyorsabb, mint a reflectionön alapuló Gson.
A Reflection a mobilplatformokon biztonsági és teljesítménybeli korlátokkal rendelkezik. Az Android az API 28-tól (Pie) kezdve korlátozza a setAccessible-t a non-SDK interfészeknél — a rejtett rendszermetódus megnyitására tett kísérlet kivételhez vagy figyelmeztetéshez vezet. Az iOS a Swift-tel nem támogatja a reflectiont a klasszikus értelemben: a Swift Mirror API csak a tulajdonságok olvasását (name, value) biztosítja, metódusok módosítása vagy hívása nélkül.
A Google Play elutasítja azokat az alkalmazásokat, amelyek a platformkorlátok megkerülésére használják a reflectiont: rendszerszolgáltatások helyettesítése, SELinux-szabályzatok módosítása, védett engedélyek olvasása. Az Apple szintén blokkolja azokat az alkalmazásokat, amelyek reflection révén privát API-t hívnak — az App Review-ellenőrzés a bináris fájlt ismert privát szelektorokkal rendelkező objc_msgSend szöveges aláírásokra szkenneli.
ProGuard/R8 — egy további korlát. A kód obfuszkálása és minifikálása rövid nevekre (a, b, c) változtatja az osztályok és metódusok neveit. Ha a kód a Class.forName(„com.example.MyClass”) hívást használja, az obfuszkálás után eltörik. A megoldás — keep-szabályok a proguard-rules.pro fájlban:
// ProGuard keep-szabályok a reflectionhez
-keep class com.example.** { *; }
-keep class * implements java.io.Serializable { *; }
-keepclassmembers class * {
@com.google.gson.annotations.SerializedName ;
}
-keepattributes Signature, InnerClasses, EnclosingMethod
A -keep szabályok utasítják az R8-at, hogy ne változtassa meg a reflection segítségével használt osztályok neveit. E szabályok nélkül az obfuszkált alkalmazás ClassNotFoundException hibával omlik össze — a runtime nem találja meg az osztályt a megváltozott szöveges név alapján.
Gyakran ismételt kérdések
Igen, a reflection 10–100-szor lassabb, mint a közvetlen hívás. A fő okok: a JIT-optimalizálások (inlining, devirtualization) hiánya, a paraméterek csomagolása és a típusok ellenőrzése minden hívásnál. Production-kód esetén ajánlott a reflectiont KSP vagy annotation processing révén code generationnel helyettesíteni.
A Java reflection a Class, Method, Field elemeken keresztül működik, és a private tagokhoz setAccessible-t igényel. A Kotlin reflection a KClass, KFunction, KProperty elemeket használja, és támogatja a sealed class, data class, korutin (suspend-függvények) és null-safety konstrukciókat. A Kotlin reflection a Java reflectionön alapul, de type-safe API-t ad hozzá.
Adjunk hozzá ProGuard/R8 keep-szabályokat a reflection segítségével használt osztályokhoz, metódusokhoz és mezőkhöz. Minden Class.forName(), getDeclaredMethod(), getDeclaredField() híváshoz tartoznia kell a megfelelő -keep direktívának. Az olyan eszközök, mint a GreenDAO és a Room, automatikusan generálják a keep-szabályokat.
A Swift nem rendelkezik reflectionnel a teljes értelemben. A Mirror API (Swift 2+) lehetővé teszi a struktúra vagy osztály tulajdonságainak olvasását: név, érték, típus. A metódusok meghívása, a mezők módosítása és a típus alapján történő példányosítás nem lehetséges. Ehhez az Objective-C Runtime használható az NSObject-ből történő öröklésnél, @objc dynamic jelöléssel.
Gson (JSON-szerializáció), Retrofit (interfész-implementációk létrehozása dynamic proxy révén), Mockito (mock-ok létrehozása), Koin (dependency injection), Room (az Entity ellenőrzése a fordítási fázisban KAPT révén), Firebase Crashlytics (stack trace elemzés). A legtöbb könyvtár a KSP/KAPT alapú code generationre tér át.
Összegzés
Kulcsrakész mobilalkalmazást fejlesztünk
Az IT Sectr 2017 óta készít iOS és Android alkalmazásokat induló vállalkozásoknak és vállalkozásoknak. Tanácsot adunk, és a legjobb megoldást javasoljuk.
Olvassa el is