Рефлексия
Рефлексия — это набор языковых и библиотекных функций, которые позволяют вам инспектировать структуру вашей программы во время выполнения. Функции и свойства являются полноценными элементами в Kotlin, а возможность их инспектирования (например, узнать имя или тип свойства или функции во время выполнения) имеет важное значение при использовании функционального или реактивного стиля.
Зависимость от JVM
На платформе JVM дистрибутив компилятора Kotlin включает в себя компонент времени выполнения, необходимый для использования функций рефлексии, как отдельный артефакт, kotlin-reflect.jar. Это делается для уменьшения размера библиотеки времени выполнения для приложений, которые не используют функции рефлексии.
Чтобы использовать рефлексию в проекте Gradle или Maven, добавьте зависимость от kotlin-reflect:
-
В Gradle:
dependencies { implementation("org.jetbrains.kotlin:kotlin-reflect:1.8.0") }dependencies { implementation "org.jetbrains.kotlin:kotlin-reflect:1.8.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 вычисляется в объект свойства типа 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> в зависимости от количества параметров.
Связанные ссылки на функции и свойства
Вы можете сослаться на метод экземпляра конкретного объекта:
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