Kotlin extension function access Java private field
Asked Answered
Y

5

45

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?

Yiddish answered 16/7, 2017 at 17:49 Comment(4)
why did you need to access the java private field?Martens
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.Yiddish
modifying the external library private field is dangerous. it maybe crush your application. you can using java reflection to change the field value.Martens
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....Yiddish
M
65

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;
    }
}
Martens answered 16/7, 2017 at 18:18 Comment(3)
That's a clean solution which resolves the issue in an inline manner. No need to define a separate extension getter. ThanksYiddish
@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.Martens
How to get a field with type of listener, like mOnCheckedChangeListener in CompoundButton?Orlosky
K
15

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.

Km answered 16/7, 2017 at 18:8 Comment(3)
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 safeYiddish
Well that's not gonna happen. No one should encourage reflection like that.Km
Anecdotal commit in the Android platform: Another day, another private field accessed. In this case Google had to rollback the rename of a private member because Facebook was accessing it through reflection.Km
G
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
Goforth answered 10/8, 2020 at 9:47 Comment(1)
Super ! Works for me. ThxLiebig
Y
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
}
Yiddish answered 16/7, 2017 at 18:15 Comment(1)
you can using Class#getDeclaredField & Field#getInt directly. since you know exactly what field you want to get.Martens
R
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

Robotize answered 20/7, 2019 at 12:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.