Рефлексия
Рефлексия — это набор возможностей языка и библиотек, который позволяет интроспектировать программу (обращаться к её структуре) во время её исполнения. В Kotlin функции и свойства первичны, и поэтому их интроспекция (например, получение имени или типа во время исполнения) сильно переплетена с использованием функциональной или реактивной парадигмы.
Kotlin/JS предоставляет ограниченную поддержку рефлексии. Узнайте больше об рефлексии в Kotlin/JS.
JVM зависимость
На платформе JVM дистрибутив компилятора Kotlin включает в себя компонент среды выполнения, необходимый для
использования рефлексии в качестве отдельного артефакта, kotlin-reflect.jar
. Это сделано для уменьшения требуемого
размера runtime-библиотеки для приложений, которые не используют рефлексию.
Чтобы использовать рефлексию в проекте Gradle или Maven, добавьте зависимость kotlin-reflect
:
- В Gradle:
// Kotlin
dependencies {
implementation("org.jetbrains.kotlin:kotlin-reflect:%kotlinVersion%")
}
// Groovy
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, использующие компилятор командной строки или Ant), он добавляется
по умолчанию. В компиляторе командной строки и Ant вы можете использовать опцию компилятора -no-reflect
, чтобы
исключить kotlin-reflect.jar
из classpath.
Ссылки на классы
Самая базовая возможность рефлексии — это получение ссылки на Kotlin класс. Чтобы получить ссылку на статический Kotlin класс, используйте синтаксис литерала класса.
val c = MyClass::class
Ссылка на класс имеет тип KClass.
Ссылка на Kotlin класс это не то же самое, что и ссылка на Java класс. Для получения ссылки на Java класс, используйте свойство
.java
экземпляра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
В качестве альтернативы вы можете передать её как значение, например в другую функцию.
Чтобы сделать это, используйте оператор ::
.
val numbers = listOf(1, 2, 3)
println(numbers.filter(::isOdd)) // выведет [1, 3]
Здесь, ::isOdd
— значение функционального типа (Int) -> Boolean
.
Ссылки на функции принадлежат к одному из подтипов
KFunction<out R>
, в зависимости
от количества параметров. Например, KFunction3<T1, T2, T3, R>
.
Оператор ::
может быть использован с перегруженными функциями, когда тип используемой функции известен из контекста.
Например:
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)
Также вместо этого вы можете указать нужный контекст путём сохранения ссылки на функцию в переменной, тип которой задан явно.
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 length(s: String) = s.length
val oddLength = compose(::isOdd, ::length)
val strings = listOf("a", "ab", "abc")
println(strings.filter(oddLength)) // выведет "[a, abc]"
Ссылки на свойства
Для доступа к свойствам как первичным объектам в 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) // выведет 2
}
Ссылка на свойство может быть использована там, где ожидается функция с одним обобщённым параметром.
val strs = listOf("a", "bc", "def")
println(strs.map(String::length)) // выведет [1, 2, 3]
Для доступа к свойству, которое является членом класса, укажите класс.
class A(val p: Int)
fun main() {
val prop = A::p
println(prop.get(A(1))) // выведет "1"
}
Для функции-расширения:
val String.lastChar: Char
get() = this[length - 1]
fun main() {
println(String::lastChar.get("abc")) // выведет "c"
}
Взаимодействие с рефлексией Java
На платформе JVM стандартная библиотека Kotlin содержит расширения, которые сопоставляют расширяемые
ими объекты рефлексии Kotlin с объектами рефлексии Java (см. пакет kotlin.reflect.jvm
). К примеру, для нахождения поля
или 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>
в зависимости
от количества параметров.
Привязанные функции и свойства
Вы можете сослаться на метод экземпляра конкретного объекта.
val numberRegex = "\\d+".toRegex()
println(numberRegex.matches("29")) // выведет "true"
val isNumber = numberRegex::matches
println(isNumber("29")) // выведет "true"
Вместо вызова метода matches
напрямую, в примере используется ссылка на него. Такие ссылки привязаны к объектам, к
которым относятся. Их можно вызывать напрямую (как в примере выше) или использовать всякий раз, когда ожидается
выражение функционального типа.
val numberRegex = "\\d+".toRegex()
val strings = listOf("abc", "124", "a70")
println(strings.filter(numberRegex::matches)) // выведет "[124]"
Сравним типы привязанных и соответствующих непривязанных ссылок. Объект-приёмник “прикреплён” к привязанной ссылке, поэтому тип приёмника больше не является параметром.
val isNumber: (CharSequence) -> Boolean = numberRegex::matches
val matches: (Regex, CharSequence) -> Boolean = Regex::matches
Ссылка на свойство может быть также привязанной.
val prop = "abc"::length
println(prop.get()) // выведет "3"
Вам не нужно указывать this
в качестве приёмника: this::foo
и ::foo
эквивалентны.
Ссылки на привязанные конструкторы
Привязанная ссылка на конструктор внутреннего класса может быть получена путем предоставления экземпляра внешнего класса.
class Outer {
inner class Inner
}
val o = Outer()
val boundInnerCtor = o::Inner