1. Introduction
When working with Kotlin, we sometimes need to access private Java fields from Kotlin extension functions.
In this tutorial, we’ll look at how we can access private properties from Kotlin extension functions. We’ll also see what challenges this implies, and what workarounds we have at our disposal to solve the problem.
2. Extension Functions
Before digging in, we need to briefly revisit Kotlin’s extension functions API and how Kotlin implements them.
2.1. Extension Functions – API
Extension functions allow us to add functionality to a given class or interface, without modifying the original implementation. These are most useful when we’re dealing with classes from external dependencies. For instance, in Java, if we want to add a method containsIgnoreCase() to a String, we can’t do it directly:
public class StringWrapper {
private final String source;
public StringWrapper(String source) {
this.source = source;
}
public boolean containsIgnoreCase(String other) {
if (source == null) {
return false;
}
return source.toLowerCase().contains(other.toLowerCase());
}
public String getSource() {
return source;
}
}
Here, we utilized the decorator design pattern. In other words, we added functionality over String without compromising the original interface. We need to use a pattern like this since there’s no way to modify the String class in Java. However, in Kotlin, an extension function can do the job concisely:
fun String?.containsIgnoreCase(target: String) : Boolean {
if (this == null) {
return false
}
return this.lowercase().contains(target.lowercase())
}
Now, we can directly call containsIgnoreCase() on any String instance. However, it comes with a few drawbacks that we’ll go over next.
2.2. Extension Functions – Implementation Details
To understand how to access private fields from extension functions, let’s explore how Kotlin implements extension functions. We all know that Kotlin produces the same bytecode as Java. However, we generally cannot dynamically add methods to pre-compiled classes. Strictly speaking, there are some ways to achieve this in Java with bytecode instrumentation, but this technique is much more complex and is not a natural way to solve this problem.
The question then is: How does Kotlin implement extension functions? Kotlin implements extension functions by creating a new static function that accepts an invocation target object as an argument along with all declared arguments. For instance, in our example, the decompiled Java method would look like:
/**
* @param thisString - it is 'this' object, i.e. object on which the function was called in the
* original source code
* @param target - this is the parameter that was passed to the original function in the source code
*/
public static final boolean containsIgnoreCase(@Nullable String thisString, @NotNull String target) {
// And here is the implementation
}
In all the places where containsIgnoreCase() is invoked, the compiler directs all calls to this static function instead. So, Kotlin implements extension functions by generating static utility functions and delegating all appropriate calls to this function.
3. Implied Limitations
This implementation is great but leads to a problem: If the extension function compiles into a static function that accepts the target object as a parameter, how do we access the private members? Indeed, since the extension function is not a function of the extended object in the bytecode, it can’t use the target object’s private fields or methods. They are inaccessible to extension functions. So, in this regard, extension functions are different from the other functions of the object.
Therefore, we need to ask how we access private members of a class from outside.
Typically, we use reflection to access private members from outside. For instance, if we want to access the private value member of a String, then the following would work:
fun String?.containsIgnoreCase(target: String) : Boolean {
if (this == null) {
return false
}
val loadedClass = String::class
val valueField = loadedClass.java.getDeclaredField("value")
valueField?.isAccessible = true
val actualValue = valueField?.get(this) as? ByteArray
println(actualValue?.contentToString())
return this.lowercase().contains(target.lowercase())
}
This is the only way to access private fields from extension functions. Specifically, it’s more of a workaround than a real solution since private members aren’t accessible from outside the class normally. So, it’s best to avoid accessing private members in extension functions.
4. Conclusion
In this article, we’ve explored how Kotlin implements extension functions, and what limitations it implies. We also reviewed the workaround to access private members of the original object from the extension function. The Java Reflection API helps us solve this problem. However, this is a workaround, and accessing a private member outside the class should generally be avoided.
As always, the source code for this article is available over on GitHub.