Рефлексия
Рефлексия — это набор языковых и библиотечных функций, которые позволяют исследовать структуру вашей программы во время выполнения. Kotlin делает функции и свойства гражданами первого класса в языке, и их интроспекция (т. е. изучение имени или типа свойства или функции во время выполнения) тесно связана с простым использованием функционального или реактивного стиля.
На платформе JavaScript в настоящее время поддерживаются только ссылки на классы. Узнайте больше о рефлексии в Kotlin/JS.
Зависимость от JVM
На платформе JVM необходимый компонент среды выполнения для использования функций рефлексии распространяется как отдельный артефакт kotlin-reflect.jar в дистрибутиве компилятора Kotlin. Это делается для уменьшения размера библиотеки времени выполнения для приложений, которые не используют функции рефлексии.
Чтобы использовать рефлексию в проекте Gradle или Maven, добавьте зависимость от kotlin-reflect:
- В Gradle:
dependencies { implementation "org.jetbrains.kotlin:kotlin-reflect:1.4.10" }dependencies { implementation("org.jetbrains.kotlin:kotlin-reflect:1.4.10") }- В 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.
Обратите внимание, что ссылка на класс Kotlin отличается от ссылки на класс Java. Чтобы получить ссылку на класс Java, используйте свойство .java экземпляра KClass.
Связанные ссылки на классы (с версии 1.1)
Вы можете получить ссылку на класс конкретного объекта с тем же синтаксисом ::class, используя объект как получатель:
val widget: Widget = ...
assert(widget is GoodWidget) { "Bad widget: ${widget::class.qualifiedName}" }
Вы получаете ссылку на точный класс объекта, например GoodWidget или BadWidget, несмотря на тип выражения получателя (Widget).
Ссылки на вызываемые элементы
Ссылки на функции, свойства и конструкторы, помимо интроспекции структуры программы, также могут вызываться или использоваться как экземпляры типов функций.
Общий супертип для всех ссылок на вызываемые элементы — KCallable<out R>, где R — тип возвращаемого значения, который является типом свойства для свойств и типом создаваемого объекта для конструкторов.
Ссылки на функции
Когда у нас есть объявленная функция с именем так:
fun isOdd(x: Int) = x % 2 != 0
Мы можем легко вызвать её напрямую (isOdd(5)), но также можем использовать её как значение типа функции, например, передать её в другую функцию. Для этого используем оператор :::
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 вычисляет объект свойства типа KProperty<Int>, который позволяет нам читать его значение с помощью get() или получать имя свойства с помощью свойства name. Для получения дополнительной информации обратитесь к документации по классу KProperty.
Для изменяемого свойства, например var y = 1, ::y возвращает значение типа KMutableProperty<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> в зависимости от количества параметров.
Связанные ссылки на функции и свойства (с версии 1.1)
Вы можете сослаться на метод экземпляра конкретного объекта:
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
}
С версии Kotlin 1.2, явное указание this в качестве получателя не требуется: this::foo и ::foo эквивалентны.
Связанные ссылки на конструкторы
Связанная ссылка на вызываемый элемент конструктора вложенного внутреннего класса может быть получена путём предоставления экземпляра внешнего класса:
class Outer {
inner class Inner
}
val o = Outer()
val boundInnerCtor = o::Inner
© 2010–2020 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/reference/reflection.html