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