Spec-Zone.ru › Kotlin 1.7

Рефлексия

Рефлексия — это набор языковых и библиотечных функций, которые позволяют вам инспектировать структуру вашей программы во время выполнения. Функции и свойства являются первыми классами в Kotlin, и способность инспектировать их (например, узнавать имя или тип свойства или функции во время выполнения) необходима при использовании функционального или реактивного стиля.

Kotlin/JS предоставляет ограниченную поддержку функций рефлексии. Узнайте больше о рефлексии в Kotlin/JS.

Зависимость от 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.

В JVM: ссылка на 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

В качестве альтернативы вы можете использовать функцию как значение типа функции, то есть передать её в другую функцию. Для этого используйте оператор :::

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
Последнее изменение: 07 декабря 2021
Деструктурирующие объявления Начало работы с Kotlin Multiplatform Mobile

© 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

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API