Reflection in Kotlin

Burak Taşcı
2 min readJun 19, 2024

--

Hi, this is Burak. In this article, we’ll discuss reflection in Kotlin. To comprehend the topic thoroughly, we’ll cover its drawbacks, positive and negative effects, usages, and more.

Reflection allows developers to access otherwise inaccessible variables, methods, and constructors. This enables workarounds in scenarios such as:

  • Inability to reach the developers of a third-party library while needing to access private properties or methods.
  • Inspecting a class’ private properties or methods for in-depth analysis.

How to Implement?

First, ensure you add the Kotlin reflect dependency to your project:

implementation("org.jetbrains.kotlin:kotlin-reflect:latest")
  1. Filter by Name or Needed Qualifications: Filter properties, constructors, and methods by their names or specific criteria.
  2. Override Accessibility: Make private members accessible using isAccessible.
  3. Get or Call It: Retrieve the value or invoke the method once accessible.
inline fun <reified T : Any, reified R> T.getPropertyValue(propertyName: String): R? {
return T::class
.declaredMemberProperties
.firstOrNull { it.name == propertyName }
?.apply { isAccessible = true }
?.get(this) as? R
}

inline fun <reified T : Any> KClass<T>.createObject(vararg parameters: Any?): T? {
for (constructor in T::class.constructors) {
if (constructor.parameters.size == parameters.size){
constructor.isAccessible = true
runCatching {
return constructor.call(*parameters)
}
}
}
return null
}

inline fun <reified T, reified R> T.callMethod(methodName: String, vararg args: Any?): R? {
return T::class
.declaredMemberFunctions
.firstOrNull { it.name == methodName }
?.apply { isAccessible = true }
?.call(this, *args) as? R
}

How to Use?

With the above information, let’s create an example:

Class TestAccount private constructor(){

var balance: Int = 4000

private val username: String = "burak"

private var country: String = ""

private constructor(
country: String
): this(){
this.country = country
}

private fun getTaxRatio(): Float {
return 0.08f
}

protected fun sendMoney(money: Int){
balance -= money
}

}

fun main() {

val account: TestAccount = TestAccount::class.createObject() ?: return

println(account.balance)

val username = account.getPropertyValue<TestAccount, String>("username")
println(username)

val taxRatio = account.callMethod<TestAccount, Float>("getTaxRatio")
println(taxRatio)

account.callMethod<TestAccount, Unit>("sendMoney", 1200)
println(account.balance)
}

Output:

4000
burak
0.08
2800

Isn’t It Great?

Using reflection offers significant flexibility but also comes with several drawbacks. As Uncle Ben said, “With great power comes great responsibility.”

Lets get into this, reflection has pullbacks like:

  • Breaking inside logic of class
  • Class change over version updates of library (unreliable)
  • Proguard obsfucation conflict
  • Breaking encapsulation
  • Difficult to refactorization

Additionally, reflection is much slower compared to normal access, making it a less desirable practice. Therefore, it should be used cautiously.

In Conclusion

While Kotlin reflection provides powerful capabilities for accessing and manipulating private members, it should be employed sparingly and with a clear understanding of its potential drawbacks. Developers must weigh the benefits against the risks of breaking encapsulation, encountering performance issues, and dealing with maintenance challenges. Use reflection responsibly to ensure code remains clean, maintainable, and performant.

--

--