Reflection (рефлексија) — механизам у runtime-у који омогућава коду да истражује сопствену структуру: да добије класе, методе, поља и анотације без познавања типова у фази компилације. Овај алат лежи у основи многих мобилних фрејмворка — серијализација JSON (Gson, Moshi), dependency injection (Dagger, Koin) и тест ранери (JUnit, XCTest). Према Oracle Java Reflection Tutorial, 2024, reflection је обавезан елемент платформе Java, који користе све велике библиотеке.
Најважније
Reflection — способност програма да посматра и мења сопствену структуру и понашање током извршавања. У објектно-оријентисаним језицима то значи добијање објеката Class, Method, Field и Constructor, који представљају елементе програма као податке доступне за читање и позивање.
Термин „reflection” уведен је у заједници вештачке интелигенције 1982. године (Brian Cantwell Smith) и реализован у језику Smalltalk. У мобилном развоју reflection се први пут појавио у Java ME и Objective-C-у (1986, NextStep). Данас свака велика мобилна платформа има свој reflection API: Java/Kotlin за Android, Objective-C Runtime за iOS, Swift Mirror API за Swift.
Механизам reflection-а заснован је на метаподацима које компилатор чува у бајткоду или бинарном фајлу. Android чува потпуне информације о класама у DEX фајловима, iOS — у __objc_classlist сегменту у Mach-O. Runtime учитава те метаподатке у меморију и пружа API за њихово обилажење.
Java Reflection API се гради око класе java.lang.Class. Било који објекат у Java-и може се претворити у Class преко .getClass() или Class.forName(). Из Class-а се извлаче све методе, поља, конструктори, анотације и надкласе. Kotlin наслеђује Java reflection и додаје сопствене KClass, KFunction, KProperty из пакета 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("Својство: ${prop.name}, тип: ${prop.returnType}")
}
}
У овом примеру KClass пружа метаподатке data class User-а. declaredMemberProperties враћа листу својстава са њиховим типовима и гетерима. Kotlin reflection је тесно интегрисан са корутинама: KFunction подржава suspend-модификатор, што омогућава позивање асинхроних метода преко reflection-а.
Java reflection ради са Class<?>, Method.setAccessible() и Field.get(). setAccessible(true) искључује проверу приступа Java language access control за private елементе. То је моћан, али опасан механизам: на Android-у од API 28 позив setAccessible на скривене системске методе може изазвати InaccessibleObjectException.
// Java reflection: позив приватног метода
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");
Код демонстрира Class.forName() — динамичко учитавање класе по стрингу имена. То је основа архитектура за додатке: класа може бити непозната у фази компилације, али учитана и извршена преко reflection-а у runtime-у. getDeclaredMethod(„privateMethod”, ...) проналази методу по имену и типовима параметара, а invoke је извршава.
Objective-C runtime пружа функције class_copyMethodList, class_copyPropertyList, objc_getAssociatedObject. За разлику од Java-е, Objective-C не скрива приватне методе подразумевано — runtime види све методе класе. То објашњава зашто method swizzling ради без setAccessible: runtime нема инкапсулацију на нивоу метаподатака.
Reflection се користи у кључним библиотекама мобилног развоја. Серијализација JSON (Gson, Moshi, Kotlinx.serialization) добија својства објекта преко reflection-а и повезује их са JSON кључевима. Dependency injection (Dagger, Koin, Swinject) анализира конструкторе и поља за аутоматско убризгавање зависности. ORM библиотеке (Room, Realm) користе reflection за мапирање класа на табеле базе података.
Свака од ових примена ради управо у runtime-у — код не зна унапред са којим класама ће се сусрести. Reflection пружа универзални механизам за превазилажење те непознатости по цену перформанси и безбедности.
Reflection је 10–100 пута спорији од директног позивања метода. Разлог — недостатак JIT оптимизација (devirtualization, inlining), провера типова при сваком позиву и паковање параметара у Object[]/varargs. ART на Android 14 не може inline-оптимизовати reflection позиве, јер циљни метод није познат до тренутка извршења.
| Операција | Директан позив | Преко Reflection-а | Успоравање |
|---|---|---|---|
| Позив методе без параметара | ~3 нс | ~120 нс | 40x |
| Читање int поља | ~1 нс | ~85 нс | 85x |
| Позив методе са 2 параметра | ~4 нс | ~250 нс | 62x |
| Креирање инстанце преко конструктора | ~5 нс | ~180 нс | 36x |
| Одређивање класе по стрингу | — | ~800 нс | — |
Подаци су добијени на Google Pixel 8 (Android 14, ART). Перформансе reflection-а се побољшавају са сваком верзијом Android-а: на Android 9 позив преко Method.invoke() био је 150 пута спорији од директног, на Android 14 — 40 пута. ART користи уграђене механизме method handle за оптимизацију.
За делове критичне по перформансе, програмери замењују reflection кодогенерацијом: Dagger користи annotation processing уместо runtime-претраге, Kotlinx.serialization генерише серијализаторе преко KSP, Moshi прилагођава @JsonClass(generateAdapter = true) за codegen у фази компилације.
Annotation processing (KAPT, KSP) и code generation — главне алтернативе за reflection у мобилном развоју. Оне премештају анализу метаподатака са runtime-а на compile time: код се генерише пре покретања апликације, што елиминише reflection overhead и побољшава перформансе.
// KSP: code generation уместо reflection-а
@Serializable
data class Config(
val apiUrl: String,
val timeout: Int
)
// KSP генерише ConfigSerializer без reflection-а
fun loadConfig(json: String): Config {
return Config.serializer().decodeFromString(json)
}
У овом примеру @Serializable је анотација Kotlinx.serialization. KSP (Kotlin Symbol Processing) анализира изворни код у фази компилације, проналази све @Serializable класе и генерише серијализаторе. Током извршавања апликације reflection се не користи — серијализатор је већ компилиран у машински код.
Code generation обезбеђује боље перформансе, безбедност типова и мању величину бинарног фајла (dead code elimination уклања неискоришћене reflection зависности). Reflection остаје неопходан за задатке где су типови непознати у фази компилације: динамичко учитавање додатака, runtime-прокси, инструментирање тестова. Према подацима Kotlin-а, Kotlinx.serialization са KSP је 3–5 пута бржи од Gson-а заснованог на reflection-у.
Reflection на мобилним платформама има безбедносна и перформансна ограничења. Android од API 28 (Pie) ограничава setAccessible за non-SDK интерфејсе — покушај отварања скривене системске методе доводи до изузетка или упозорења. iOS са Swift-ом не подржава reflection у класичном смислу: Swift Mirror API пружа само читање својстава (name, value) без измене или позивања метода.
Google Play одбија апликације које користе reflection за заобилажење ограничења платформе: замену системских сервиса, измену SELinux политика, читање заштићених пермисија. Apple такође блокира апликације које позивају приватне API преко reflection-а — провера App Review скенира бинарни фајл за стринг потписе objc_msgSend са познатим приватним селекторима.
ProGuard/R8 — још једно ограничење. Обфускација и минификација кода преименују класе и методе у кратка имена (a, b, c). Ако код користи Class.forName(„com.example.MyClass”), поквариће се након обфускације. Решење — keep правила у proguard-rules.pro:
// ProGuard keep правила за reflection
-keep class com.example.** { *; }
-keep class * implements java.io.Serializable { *; }
-keepclassmembers class * {
@com.google.gson.annotations.SerializedName ;
}
-keepattributes Signature, InnerClasses, EnclosingMethod
Правила -keep обавештавају R8 да не преименује класе коришћене преко reflection-а. Без ових правила, обфускована апликација пашће са ClassNotFoundException — runtime неће моћи да пронађе класу по стрингу имена, који се променио.
Често постављана питања
Да, reflection је 10–100 пута спорији од директног позива. Главни разлози: недостатак JIT оптимизација (inlining, devirtualization), паковање параметара и провера типова при сваком позиву. За production код препоручује се замена reflection-а code generation-ом преко KSP или annotation processing-а.
Java reflection ради преко Class, Method, Field и захтева setAccessible за private чланове. Kotlin reflection користи KClass, KFunction, KProperty и подржава sealed class, data class, корутине (suspend функције) и null-safety. Kotlin reflection је заснован на Java reflection-у, али додаје type-safe API.
Додати ProGuard/R8 keep правила за класе, методе и поља коришћене преко reflection-а. За сваки Class.forName(), getDeclaredMethod(), getDeclaredField() мора постојати одговарајућа директива -keep. Алати попут GreenDAO и Room аутоматски генеришу keep правила.
Swift нема reflection у потпуном смислу. Mirror API (Swift 2+) омогућава читање својстава структуре или класе: име, вредност, тип. Позивање метода, измена поља и креирање инстанци по типу нису могући. За то се користи Objective-C Runtime при наслеђивању од NSObject са @objc dynamic.
Gson (JSON серијализација), Retrofit (креирање implementation интерфејса преко dynamic proxy-ја), Mockito (креирање мокова), Koin (dependency injection), Room (провера Entity у фази компилације преко KAPT), Firebase Crashlytics (анализа stack trace-а). Већина библиотека прелази на code generation са KSP/KAPT.
Закључци
Развићемо мобилну апликацију под кључ
IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође