Рефлексия
Рефлексия — это набор языковых и библиотечных функций, позволяющих вам инспектировать структуру вашей программы во время выполнения. Функции и свойства являются первоклассными гражданами в Kotlin, а возможность инспектировать их (например, узнавать имя или тип свойства или функции во время выполнения) является важной при использовании функционального или реактивного стиля.
Зависимость от JVM
На платформе JVM дистрибутив компилятора Kotlin включает в себя компонент времени выполнения, необходимый для использования функций рефлексии, как отдельный артефакт, kotlin-reflect.jar. Это делается для уменьшения размера библиотеки времени выполнения для приложений, которые не используют функции рефлексии.
Для использования рефлексии в проектах Gradle или Maven добавьте зависимость от kotlin-reflect:
-
В Gradle:
dependencies { implementation("org.jetbrains.kotlin:kotlin-reflect:1.6.20") }dependencies { implementation "org.jetbrains.kotlin:kotlin-reflect:1.6.20" } -
В 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–2022 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/reflection.html