Рефлексия
Рефлексия — это набор возможностей языка и библиотек, который позволяет интроспектировать структуру программы во время ее исполнения. В Kotlin функции и свойства являются полноправными сущностями, поэтому возможность интроспектировать их (например, узнавать имя или тип свойства либо функции во время выполнения) важна при использовании функционального или реактивного стиля.
Kotlin/JS предоставляет ограниченную поддержку возможностей рефлексии. Подробнее о рефлексии в Kotlin/JS. {:.note}
Зависимость для JVM
На платформе JVM дистрибутив компилятора Kotlin включает компонент среды выполнения, необходимый для использования
рефлексии, как отдельный артефакт kotlin-reflect.jar. Это сделано, чтобы уменьшить размер runtime-библиотеки для
приложений, которые не используют рефлексию.
Чтобы использовать рефлексию в проекте Gradle или Maven, добавьте зависимость kotlin-reflect:
- В Gradle:
dependencies {
implementation(kotlin("reflect"))
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-reflect:%kotlinVersion%"
}
- В Maven:
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-reflect</artifactId>
</dependency>
</dependencies>
Если вы не используете Gradle или Maven, убедитесь, что kotlin-reflect.jar добавлен в classpath вашего проекта.
В других поддерживаемых случаях, например в проектах IntelliJ IDEA, которые используют компилятор командной строки,
он добавляется по умолчанию. В компиляторе командной строки можно использовать опцию -no-reflect, чтобы исключить
kotlin-reflect.jar из classpath.
Ссылки на классы
Самая базовая возможность рефлексии — получение ссылки времени выполнения на Kotlin-класс. Чтобы получить ссылку на статически известный Kotlin-класс, используйте синтаксис литерала класса:
val c = MyClass::class
Ссылка имеет тип KClass.
На JVM ссылка на Kotlin-класс не совпадает со ссылкой на Java-класс. Чтобы получить ссылку на Java-класс, используйте свойство
.javaу экземпляраKClass. {:.note}
Ссылки на привязанные классы
Вы можете получить ссылку на класс конкретного объекта с помощью того же синтаксиса ::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
}
{kotlin-runnable=“true” kotlin-min-compiler-version=“1.3”}
Здесь ::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)) // ссылается на isOdd(x: Int)
//sampleEnd
}
{kotlin-runnable=“true” kotlin-min-compiler-version=“1.3”}
Также можно предоставить нужный контекст, сохранив ссылку на метод в переменной с явно указанным типом:
val predicate: (String) -> Boolean = ::isOdd // ссылается на 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-runnable=“true” kotlin-min-compiler-version=“1.3”}
Ссылки на свойства
Чтобы обращаться к свойствам как к полноправным объектам в 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)
}
{kotlin-runnable=“true” kotlin-min-compiler-version=“1.3”}
Ссылку на свойство можно использовать там, где ожидается функция с одним обобщенным параметром:
fun main() {
//sampleStart
val strs = listOf("a", "bc", "def")
println(strs.map(String::length))
//sampleEnd
}
{kotlin-runnable=“true” kotlin-min-compiler-version=“1.3”}
Чтобы обратиться к свойству-члену класса, квалифицируйте его так:
fun main() {
//sampleStart
class A(val p: Int)
val prop = A::p
println(prop.get(A(1)))
//sampleEnd
}
{kotlin-runnable=“true” kotlin-min-compiler-version=“1.3”}
Для свойства-расширения:
val String.lastChar: Char
get() = this[length - 1]
fun main() {
println(String::lastChar.get("abc"))
}
{kotlin-runnable=“true” kotlin-min-compiler-version=“1.3”}
Взаимодействие с рефлексией Java
На платформе JVM стандартная библиотека содержит расширения для классов рефлексии, которые обеспечивают сопоставление
с объектами рефлексии Java и обратно (см. пакет kotlin.reflect.jvm). Например, чтобы найти backing field или Java-метод,
который служит геттером для Kotlin-свойства, можно написать так:
import kotlin.reflect.jvm.*
class A(val p: Int)
fun main() {
println(A::p.javaGetter) // выведет "public final int A.getP()"
println(A::p.javaField) // выведет "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
}
{kotlin-runnable=“true” kotlin-min-compiler-version=“1.3”}
Вместо прямого вызова метода matches в примере используется ссылка на него. Такая ссылка привязана к своему получателю.
Ее можно вызвать напрямую, как в примере выше, или использовать везде, где ожидается выражение функционального типа:
fun main() {
//sampleStart
val numberRegex = "\\d+".toRegex()
val strings = listOf("abc", "124", "a70")
println(strings.filter(numberRegex::matches))
//sampleEnd
}
{kotlin-runnable=“true” kotlin-min-compiler-version=“1.3”}
Сравните типы привязанных и непривязанных ссылок. У привязанной вызываемой ссылки получатель уже “прикреплен”, поэтому тип получателя больше не является параметром:
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-runnable=“true” kotlin-min-compiler-version=“1.3”}
Указывать this как получатель не нужно: this::foo и ::foo эквивалентны.
Привязанные ссылки на конструкторы
Привязанную вызываемую ссылку на конструктор внутреннего класса можно получить, предоставив экземпляр внешнего класса:
class Outer {
inner class Inner
}
val o = Outer()
val boundInnerCtor = o::Inner