32
votes

I'd like to access Java's private field when using Kotlin extension function.

Suppose I have a Java class ABC. ABC has only one private field mPrivateField. I'd like to write an extension function in Kotlin which uses that field for whatever reason.

public class ABC {
    private int mPrivateField;

}

the Kotlin function would be:

private fun ABC.testExtFunc() {
    val canIAccess = this.mPrivateField;
}

the error I'm getting is:

Cannot access 'mPrivateField': It is private in 'ABC'

Any way of getting around that limitation?

5
why did you need to access the java private field?holi-java
it is from an external, compiled library that I'd like to extend without including the whole source code in my project. The library has "get" methods for the Android Calendar events and I wanted to add the "insert calendar event" functionality into to class.kosiara - Bartosz Kosarzycki
modifying the external library private field is dangerous. it maybe crush your application. you can using java reflection to change the field value.holi-java
Yeah, I'd agree. But I Just want to get that field. And use its methods just as the class that I'm extending does. No real threat here....kosiara - Bartosz Kosarzycki

5 Answers

52
votes

First, you need to obtain a Field and enable it can be accessible in Kotlin, for example:

val field = ABC::class.java.getDeclaredField("mPrivateField")

field.isAccessible = true

Then, you can read the field value as Int by Field#getInt from the instance of the declaring class, for example:

val it: ABC = TODO()

val value = field.getInt(it)

Last, your extension method is looks like as below:

private inline fun ABC.testExtFunc():Int {
    return javaClass.getDeclaredField("mPrivateField").let {
        it.isAccessible = true
        val value = it.getInt(this)
        //todo
        return@let value;
    }
}
11
votes

That is not possible by design. Extension functions essentially resolve to static functions with the receiver as its first parameter. Thus, an extension function

fun String.foo() {
  println(this)
}

compiles to something like:

public static void foo(String $receiver) {
  System.out.println($receiver);
}

Now it's clear to see you cannot access private member of $receiver, since they're, well, private.

If you really want to access that member, you could do so using reflection, but you'll lose all guarantees.

4
votes

Get private variable using below extension functions

fun <T : Any> T.getPrivateProperty(variableName: String): Any? {
    return javaClass.getDeclaredField(variableName).let { field ->
        field.isAccessible = true
        return@let field.get(this)
    }
}

Set private variable value get the variable

fun <T : Any> T.setAndReturnPrivateProperty(variableName: String, data: Any): Any? {
    return javaClass.getDeclaredField(variableName).let { field ->
        field.isAccessible = true
        field.set(this, data)
        return@let field.get(this)
    }
}

Get variable use:

val bool = <your_class_object>.getPrivateProperty("your_variable") as String

Set and get variable use:

val bool = <your_class_object>.setAndReturnPrivateProperty("your_variable", true) as Boolean
val str = <your_class_object>.setAndReturnPrivateProperty("your_variable", "Hello") as String
3
votes

Just as nhaarman suggested I used reflection to access the field in question. Specifically I created a getter which used reflection internally on the class mentioned (that is ABC)

Sadly accessing private fields in Kotlin extension function is not possible as of July 2017

fun ABC.testExtFunc() {
    val canIAccess = this.getmPrivateField()
}

fun ABC.getmPrivateField() : Int {
    val field = this.javaClass.declaredFields
            .toList().filter { it.name == "mPrivateField" }.first()
    field.isAccessible = true
    val value = field.get(this)
    return value as Int
}
1
votes

Extending holi-java's answer with a generic type:

  1. Create extension
fun<T: Any> T.accessField(fieldName: String): Any? {
    return javaClass.getDeclaredField(fieldName).let { field ->
        field.isAccessible = true
        return@let field.get(this)
    }
}

  1. Access private field
val field = <your_object_instance_with_private_field>
                .accessField("<field_name>")
                    as <object_type_of_field_name>

Example:

class MyClass {

    private lateinit var mObject: MyObject

}

val privateField = MyClass()
                .accessField("mObject")
                    as MyObject