Kotlin扩展函数如何访问Java私有字段

44

我想在使用Kotlin的扩展函数时访问Java的私有字段

假设我有一个JavaABCABC只有一个私有字段mPrivateField。我想编写一个Kotlin扩展函数,以任何方式使用该字段。

public class ABC {
    private int mPrivateField;

}

Kotlin函数将是:

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

我遇到的错误是:

无法访问'mPrivateField':它在'ABC'中是私有的

有什么方法可以规避这个限制吗?


你为什么需要访问Java中的私有字段? - holi-java
这是关于编程的内容,需要将其从英语翻译成中文。请仅返回已翻译的文本:我想扩展一个外部编译库,而不必在我的项目中包含整个源代码。该库具有用于获取Android日历事件的“get”方法,我希望将“插入日历事件”功能添加到该类中。 - kosiara - Bartosz Kosarzycki
1
修改外部库的private字段是危险的,它可能会破坏您的应用程序。您可以使用Java 反射来更改字段的值。 - holi-java
是的,我同意。但我只想获取那个字段。并像我正在扩展的类一样使用它的方法。这里没有真正的威胁... - kosiara - Bartosz Kosarzycki
5个回答

63

首先,您需要获取一个Field并使其在Kotlin中可以被访问,例如:

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

field.isAccessible = true

然后,你可以通过从声明类的实例中使用Field#getInt方法读取字段值为Int,例如:

val it: ABC = TODO()

val value = field.getInt(it)

最后,您的扩展方法看起来如下:

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

这是一个干净的解决方案,以内联方式解决了问题。无需定义单独的扩展获取器。谢谢。 - kosiara - Bartosz Kosarzycki
@kosiara-BartoszKosarzycki 当然可以,如果你愿意的话,你可以将该函数变成一个内联函数。但是,当你想要使操作抽象化时,扩展是必要的,例如:testExtFunc(action:(Int)->R),将读取逻辑与action逻辑分离,并且您可以在任何地方重用读取函数testExtFunc。然后,您可以像使用let/run/apply等一样使用它,如testExtFunc(foo)testExtFunc(bar) - holi-java
如何获取类型为监听器的字段,例如CompoundButton中的mOnCheckedChangeListener - Morteza Rastgoo

15

这是设计上不可能的。扩展函数本质上解析为静态函数,并将接收器作为其第一个参数。因此,扩展函数

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

编译后的结果类似于:

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

现在可以清楚地看到,您无法访问$receiver的私有成员,因为它们是私有的。

如果您确实想要访问该成员,可以使用反射来实现,但您将失去所有保证。


希望Kotlin的开发者们能够自动化反射方法的生成,以简化私有字段的获取操作...虽然我同意在大多数情况下这样做并不安全。 - kosiara - Bartosz Kosarzycki
3
好的,那是不可能的。没有人应该鼓励这样的反思。 - nhaarman
15
Android平台的轶事:又是一天,又有一个私有字段被访问. 在这种情况下,Google不得不撤销对一个私有成员变量的更名,因为Facebook通过反射在访问该变量。 - nhaarman

14
使用以下扩展函数获取私有变量。
fun <T : Any> T.getPrivateProperty(variableName: String): Any? {
    return javaClass.getDeclaredField(variableName).let { field ->
        field.isAccessible = true
        return@let field.get(this)
    }
}

设置私有变量的值并获取该变量

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)
    }
}

获取变量的使用方法:

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

使用 set 和 get 设置和获取变量:

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

1
太棒了!对我有用。谢谢。 - Adrenal1ne

3

正如nhaarman所建议的,我使用反射来访问相关字段。具体而言,我创建了一个getter函数,该函数在提到的类(即ABC)内部使用反射。

可惜的是,截至2017年7月,在Kotlin扩展函数中访问私有字段是不可能的。

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
你可以直接使用Class#getDeclaredFieldField#getInt。因为你知道你想要获取哪个字段。 - holi-java

1

holi-java 的回答上扩展泛型类型:

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

  1. 访问私有字段
val field = <your_object_instance_with_private_field>
                .accessField("<field_name>")
                    as <object_type_of_field_name>

例子:

class MyClass {

    private lateinit var mObject: MyObject

}

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接