Рефлексия
Рефлексия — это набор языковых и библиотечных функций, позволяющих инспектировать структуру вашей программы во время выполнения. Функции и свойства являются в Kotlin объектами первого класса, и возможность их инспектирования (например, получение имени или типа свойства или функции во время выполнения) является существенной при использовании функционального или реактивного стиля.
Зависимость от JVM
На платформе JVM дистрибутив компилятора Kotlin включает в себя компонент времени выполнения, необходимый для использования функций рефлексии, как отдельный артефакт, kotlin-reflect.jar. Это делается для уменьшения размера библиотеки времени выполнения для приложений, которые не используют функции рефлексии.
Чтобы использовать рефлексию в проекте Gradle или Maven, добавьте зависимость от kotlin-reflect.
-
В Gradle:
dependencies { implementation(kotlin("reflect")) }dependencies { implementation "org.jetbrains.kotlin:kotlin-reflect:1.9.0" } -
В Maven:
<dependencies> <dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-reflect</artifactId> </dependency> </dependencies>
Если вы не используете Gradle или Maven, убедитесь, что kotlin-reflect.jar включено в пути к классам вашего проекта. В других поддерживаемых случаях (проекты IntelliJ IDEA, использующие командную строку компилятора или Ant) это добавляется по умолчанию. В командной строке компилятора и Ant можно использовать параметр компилятора -no-reflect, чтобы исключить kotlin-reflect.jar из пути к классам.
Ссылки на классы
Наиболее базовая функция рефлексии — получение ссылки на класс Kotlin во время выполнения. Для получения ссылки на статически известный класс Kotlin можно использовать синтаксис литерала класса:
val c = MyClass::class
Ссылка имеет тип KClass.
Связанные ссылки на классы
Вы можете получить ссылку на класс определенного объекта с помощью того же синтаксиса ::class, используя объект в качестве получателя:
val widget: Widget = ...
assert(widget is GoodWidget) { "Bad widget: ${widget::class.qualifiedName}" }
Вы получите ссылку на точный класс объекта, например, GoodWidget или BadWidget, независимо от типа выражения-получателя (Widget).
Ссылки на вызываемые элементы
Ссылки на функции, свойства и конструкторы также могут вызываться или использоваться как экземпляры типов функций.
Общий супертип для всех ссылок на вызываемые элементы — KCallable<out R>, где R — тип возвращаемого значения. Это тип свойства для свойств и созданный тип для конструкторов.
Ссылки на функции
Когда у вас есть объявленная функция с именем, как показано ниже, вы можете вызвать ее напрямую (isOdd(5)):
fun isOdd(x: Int) = x % 2 != 0
В качестве альтернативы вы можете использовать функцию как значение типа функции, то есть передать ее в другую функцию. Для этого используйте оператор :::
fun isOdd(x: Int) = x % 2 != 0
fun main() {
//sampleStart
val numbers = listOf(1, 2, 3)
println(numbers.filter(::isOdd))
//sampleEnd
}
Здесь ::isOdd — значение типа функции (Int) -> Boolean.
Ссылки на функции относятся к одному из подтипов KFunction<out R>, в зависимости от количества параметров. Например, KFunction3<T1, T2, T3, R>.
:: может использоваться с перегруженными функциями, когда ожидаемый тип известен из контекста. Например:
fun main() {
//sampleStart
fun isOdd(x: Int) = x % 2 != 0
fun isOdd(s: String) = s == "brillig" || s == "slithy" || s == "tove"
val numbers = listOf(1, 2, 3)
println(numbers.filter(::isOdd)) // refers to isOdd(x: Int)
//sampleEnd
}
В качестве альтернативы вы можете предоставить необходимый контекст, сохранив ссылку на метод в переменной с явно указанным типом:
val predicate: (String) -> Boolean = ::isOdd // refers to isOdd(x: String)
Если вам нужно использовать член класса или расширяющую функцию, её нужно квалифицировать: String::toCharArray.
Даже если вы инициализируете переменную ссылкой на расширяющую функцию, выведенный тип функции не будет иметь получателя, но будет иметь дополнительный параметр, принимающий объект получателя. Чтобы получить тип функции с получателем, укажите тип явно:
val isEmptyStringList: List<String>.() -> Boolean = List<String>::isEmpty
Пример: композиция функций
Рассмотрим следующую функцию:
fun <A, B, C> compose(f: (B) -> C, g: (A) -> B): (A) -> C {
return { x -> f(g(x)) }
}
Она возвращает композицию двух переданных ей функций: compose(f, g) = f(g(*)). Вы можете применить эту функцию к ссылкам на вызываемые элементы:
fun <A, B, C> compose(f: (B) -> C, g: (A) -> B): (A) -> C {
return { x -> f(g(x)) }
}
fun isOdd(x: Int) = x % 2 != 0
fun main() {
//sampleStart
fun length(s: String) = s.length
val oddLength = compose(::isOdd, ::length)
val strings = listOf("a", "ab", "abc")
println(strings.filter(oddLength))
//sampleEnd
}
Ссылки на свойства
Чтобы получить доступ к свойствам как к объектам первого класса в Kotlin, используйте оператор :::
val x = 1
fun main() {
println(::x.get())
println(::x.name)
}
Выражение ::x оценивается как объект свойства типа KProperty0<Int>. Вы можете прочитать его значение, используя get() или получить имя свойства, используя свойство name . Для получения более подробной информации см. документацию по классу KProperty.
Для изменяемого свойства, такого как var y = 1, ::y возвращает значение типа KMutableProperty0<Int>, которое имеет метод set():
var y = 1
fun main() {
::y.set(2)
println(y)
}
Ссылка на свойство может использоваться там, где ожидается функция с одним универсальным параметром:
fun main() {
//sampleStart
val strs = listOf("a", "bc", "def")
println(strs.map(String::length))
//sampleEnd
}
Чтобы получить доступ к свойству, являющемуся членом класса, квалифицируйте его следующим образом:
fun main() {
//sampleStart
class A(val p: Int)
val prop = A::p
println(prop.get(A(1)))
//sampleEnd
}
Для расширяющего свойства:
val String.lastChar: Char
get() = this[length - 1]
fun main() {
println(String::lastChar.get("abc"))
}
Взаимодействие с Java-рефлексией
На платформе JVM стандартная библиотека содержит расширения для классов рефлексии, которые обеспечивают отображение в Java-объекты рефлексии (см. пакет kotlin.reflect.jvm). Например, чтобы найти вспомогательное поле или Java-метод, выполняющий функцию геттера для свойства Kotlin, можно написать что-то подобное:
import kotlin.reflect.jvm.*
class A(val p: Int)
fun main() {
println(A::p.javaGetter) // prints "public final int A.getP()"
println(A::p.javaField) // prints "private final int A.p"
}
Чтобы получить класс Kotlin, соответствующий Java-классу, используйте расширяющее свойство .kotlin:
fun getKClass(o: Any): KClass<Any> = o.javaClass.kotlin
Ссылки на конструкторы
На конструкторы можно ссылаться так же, как на методы и свойства. Вы можете использовать их там, где программа ожидает объект типа функции, принимающий те же параметры, что и конструктор, и возвращающий объект соответствующего типа. Конструкторы ссылаются с помощью оператора :: и добавления имени класса. Рассмотрим следующую функцию, которая ожидает параметр функции без параметров и тип возвращаемого значения Foo:
class Foo
fun function(factory: () -> Foo) {
val x: Foo = factory()
}
Используя ::Foo, нулевой конструктор класса Foo, можно вызвать его так:
function(::Foo)
Ссылки на вызываемые элементы для конструкторов типизируются как один из подтипов KFunction<out R>, в зависимости от количества параметров.
Связанные ссылки на функции и свойства
Вы можете ссылаться на метод экземпляра конкретного объекта:
fun main() {
//sampleStart
val numberRegex = "\\d+".toRegex()
println(numberRegex.matches("29"))
val isNumber = numberRegex::matches
println(isNumber("29"))
//sampleEnd
}
Вместо прямого вызова метода matches пример использует ссылку на него. Такая ссылка связана с её получателем. Ее можно вызвать напрямую (как в примере выше) или использовать там, где ожидается выражение типа функции:
fun main() {
//sampleStart
val numberRegex = "\\d+".toRegex()
val strings = listOf("abc", "124", "a70")
println(strings.filter(numberRegex::matches))
//sampleEnd
}
Сравните типы связанной и несвязанной ссылок. Связанная ссылка на вызываемый элемент имеет своего получателя, поэтому тип получателя больше не является параметром:
val isNumber: (CharSequence) -> Boolean = numberRegex::matches val matches: (Regex, CharSequence) -> Boolean = Regex::matches
Ссылка на свойство также может быть связана:
fun main() {
//sampleStart
val prop = "abc"::length
println(prop.get())
//sampleEnd
}
Вам не нужно указывать this в качестве получателя: this::foo и ::foo эквивалентны.
Связанные ссылки на конструкторы
Связанную ссылку на вызываемый элемент для конструктора внутреннего класса можно получить, предоставив экземпляр внешнего класса:
class Outer {
inner class Inner
}
val o = Outer()
val boundInnerCtor = o::Inner
© 2010–2023 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/reflection.html