42

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?

  • why did you need to access the java **private** field? – holi-java Jul 16 '17 at 17:53
  • 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 Jul 16 '17 at 17:58
  • 1
    modifying the external library **private** field is *dangerous*. it maybe crush your application. you can using java [reflection](http://docs.oracle.com/javase/8/docs/api/java/lang/reflect/package-summary.html) to change the field value. – holi-java Jul 16 '17 at 18:03
  • 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 Jul 16 '17 at 18:06

5 Answers5

63

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;
    }
}
holi-java
  • 29,655
  • 7
  • 72
  • 83
  • That's a clean solution which resolves the issue in an **inline** manner. No need to define a separate extension getter. Thanks – kosiara - Bartosz Kosarzycki Jul 16 '17 at 18:30
  • @kosiara-BartoszKosarzycki Of course, you can if you like, you can make the function to an **inline** function. Howerver, the extension is necessary when you want to make an operation abstract, for example: `testExtFunc(action:(Int)->R)`, separate the read logic from the `action` logic, and you can reuse the read function `testExtFunc` anywhere. then you can uses it as, `testExtFunc(foo)` or `testExtFunc(bar)` they are just like as `let`/`run`/`apply` and .etc in kotlin. – holi-java Jul 16 '17 at 18:34
  • How to get a field with type of listener, like `mOnCheckedChangeListener` in `CompoundButton`? – Morteza Rastgoo Sep 12 '20 at 08:53
14

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.

nhaarman
  • 98,571
  • 55
  • 246
  • 278
  • Well I hope kotlin guys automate the reflection method generation to simplify private field getters in the future... although I agree that in most cases it's not safe – kosiara - Bartosz Kosarzycki Jul 16 '17 at 18:17
  • 3
    Well that's not gonna happen. No one should encourage reflection like that. – nhaarman Jul 16 '17 at 19:53
  • 15
    Anecdotal commit in the Android platform: [Another day, another private field accessed](https://android.googlesource.com/platform/libcore/+/81abb6fb7332dfe62ff596ffb250d8aec61895df%5E!/). In this case Google had to rollback the rename of a ***private*** member because Facebook was accessing it through reflection. – nhaarman Jul 16 '17 at 20:04
14

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
akhilesh0707
  • 6,709
  • 5
  • 44
  • 51
3

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
    you can using `Class#getDeclaredField` & `Field#getInt` directly. since you know exactly what field you want to get. – holi-java Jul 16 '17 at 18:21
1

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

Victor Oliveira
  • 3,293
  • 7
  • 47
  • 77