Котлин – получение экземпляра KProperty1 и CallableReference посредством отражения

Я работаю со сторонней библиотекой, в которой есть функция, которая ожидает аргумент типа 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.

Ну и основная проблема здесь — сторонняя библиотека. Там буквально написано: «мы не поддерживаем недвижимость, приобретенную через KClass». Использовать его по мере необходимости может быть сложно или даже невозможно.

broot 02.05.2024 13:22

@broot Это на самом деле обычный способ сказать: «Мы не поддерживаем свойства, приобретенные через KClass»? Это странное ограничение...

Sweeper 02.05.2024 14:25

@Sweeper Я имею в виду, им явно требовалась ссылка. Не просто какое-то свойство, а именно ссылку. Вы смогли обойти это, и это хорошо. Все-таки я думаю, что основная проблема заключается в дизайне библиотеки. В идеале они не должны делать предположений о том, какой тип KProperty был предоставлен.

broot 02.05.2024 14:46
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
3
94
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

По сути, вы можете сделать то же, что делает компилятор, когда встречает подобную ссылку на свойство — создать экземпляр PropertyReference1Impl.

Конструктор принимает 3 параметра

  • класс, в котором объявлено свойство
  • название свойства
  • сигнатура метода JVM геттера

При желании вы можете указать ему дополнительный параметр 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), который не принимает никаких параметров. Вероятно, здесь сделаны и другие предположения, которые не всегда верны, но это должно работать для большинства свойств.

Другие вопросы по теме