Я работаю со сторонней библиотекой, в которой есть функция, которая ожидает аргумент типа KProperty1<T, V>
и внутренне приводит его к экземпляру CallableReference
. Мне нужно получить ссылку на члена класса посредством отражения, которое является экземпляром как KProperty1
, так и CallableReference
.
Функция в сторонней библиотеке выглядит примерно так:
fun <T : Any, V> thirdPartyFunction(property: KProperty1<T, V>) {
val callableReference = (property as CallableReference)
}
Все работает, когда я вызываю функцию со ссылкой на член, полученной с помощью оператора ::
:
class ExampleClass(
var exampleProperty: String,
)
// this works
thirdPartyFunction(ExampleClass::exampleProperty)
Когда я вызываю функцию со значением, полученным посредством отражения, я получаю ClassCastException
:
val reflectedProperty = ExampleClass::class.memberProperties
.find { it.name == "exampleProperty" } as KProperty1<ExampleClass, String>
// class kotlin.reflect.jvm.internal.KMutableProperty1Impl cannot be cast to class kotlin.jvm.internal.CallableReference
thirdPartyFunction(reflectedProperty)
Я использую Kotlin 1.9.21 на JVM.
@broot Это на самом деле обычный способ сказать: «Мы не поддерживаем свойства, приобретенные через KClass
»? Это странное ограничение...
@Sweeper Я имею в виду, им явно требовалась ссылка. Не просто какое-то свойство, а именно ссылку. Вы смогли обойти это, и это хорошо. Все-таки я думаю, что основная проблема заключается в дизайне библиотеки. В идеале они не должны делать предположений о том, какой тип KProperty
был предоставлен.
По сути, вы можете сделать то же, что делает компилятор, когда встречает подобную ссылку на свойство — создать экземпляр PropertyReference1Impl.
Конструктор принимает 3 параметра
При желании вы можете указать ему дополнительный параметр flags, указывающий, является ли он синтетическим в мире Java и/или объявлен ли он на верхнем уровне (не в классе). Вы также можете дать ему получателя, если это свойство расширения.
Давайте рассмотрим простой случай:
class Foo {
val x = 1
}
Для Foo::x
вы должны написать:
PropertyReference1Impl(Foo::class, "x", "getX()I")
(Компилятор Kotlin фактически создаст новый класс, унаследованный от PropertyReference1Impl
, и переопределит его метод get
, чтобы напрямую возвращать значение свойства, вместо использования отражения, как это делает реализация по умолчанию.)
Подобные классы существуют для KProperty0
, KProperty2
, изменяемых свойств и функций. Пример того, как сделать это с помощью функций, смотрите мой ответ здесь
Вы можете написать такой метод, чтобы справиться с этим в более общем плане:
inline fun <reified T, R> KProperty1<T, *>.asCallableReference(): KProperty1<T, R> {
val getter = javaGetter ?: throw Exception("Must have a JVM getter for this to work!")
return PropertyReference1Impl(
T::class, name,
getter.name + MethodType.methodType(getter.returnType)
.toMethodDescriptorString()
) as KProperty1<T, R>
}
Это предполагает, что получатель взят из коллекции memberProperties
некоторого KClass
, и что это не свойство расширения, и что у него есть геттер Java (не @JvmField
), который не принимает никаких параметров. Вероятно, здесь сделаны и другие предположения, которые не всегда верны, но это должно работать для большинства свойств.
Ну и основная проблема здесь — сторонняя библиотека. Там буквально написано: «мы не поддерживаем недвижимость, приобретенную через
KClass
». Использовать его по мере необходимости может быть сложно или даже невозможно.