Skip to content

Latest commit

 

History

History
293 lines (224 loc) · 15.5 KB

reflection.md

File metadata and controls

293 lines (224 loc) · 15.5 KB
type layout category title url
doc
reference
Syntax
Рефлексия

Рефлексия

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

На платформе Java библиотека для использования рефлексии находится в отдельном JAR-файле (kotlin-reflect.jar). Это было сделано для уменьшения требуемого размера runtime-библиотеки для приложений, которые не используют рефлексию. Если вы используете рефлексию, удостоверьтесь, что этот .jar файл добавлен в classpath вашего проекта.

Ссылки на классы

Самая базовая возможность рефлексии — это получение ссылки на Kotlin класс. Чтобы получить ссылку на статический Kotlin класс, используйте синтаксис литерала класса:

val c = MyClass::class

Ссылка на класс имеет тип KClass.

Обратите внимание, что ссылка на Kotlin класс это не то же самое, что и ссылка на Java класс. Для получения ссылки на Java класс, используйте свойство .java экземпляра KClass.

Ссылки на привязанные классы

Примечание: доступно с версии Kotlin 1.1

Вы можете получить ссылку на класс определённого объекта с помощью уже известного вам синтаксиса, вызвав ::class у нужного объекта:

val widget: Widget = ...
assert(widget is GoodWidget) { "Bad widget: ${widget::class.qualifiedName}" }

Вы получите ссылку на точный класс объекта, например GoodWidget или BadWidget, несмотря на тип объекта, участвующего в выражении (Widget).

Ссылки на функции

Когда у нас есть именованная функция, объявленная следующим образом:

fun isOdd(x: Int) = x % 2 != 0

Мы можем как вызвать её напрямую (isOdd(5)), так и передать её как значение, например в другую функцию. Чтобы сделать это, используйте оператор :::

val numbers = listOf(1, 2, 3)
println(numbers.filter(::isOdd)) // выведет [1, 3]

Здесь, ::isOdd — значение функционального типа (Int) -> Boolean.

Оператор :: может быть использован с перегруженными функциями, когда тип используемой функции известен из контекста. Например:

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 даёт нам функцию-расширение для типа String: String.() -> CharArray

Пример: композиция функций

Рассмотрим следующую функцию:

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 мы по-прежнему можем использовать оператор :::

var x = 1

fun main(args: Array<String>) {
    println(::x.get()) // выведет "1"
    ::x.set(2)
    println(x)         // выведет "2"
}

Выражение ::x возвращает объект свойства типа KProperty<Int>, который позволяет нам читать его значение с помощью get() или получать имя свойства при помощи обращения к свойству name. Для получения более подробной информации обратитесь к документации класса KProperty.

Для изменяемых свойств, например var y = 1, ::y возвращает значение типа KMutableProperty<Int>.

Ссылка на свойство может быть использована там, где ожидается функция без параметров:

val strs = listOf("a", "bc", "def")
println(strs.map(String::length)) // выведет [1, 2, 3]

Для доступа к свойству, которое является членом класса, мы указываем класс:

class A(val p: Int)

fun main(args: Array<String>) {
    val prop = A::p
    println(prop.get(A(1))) // выведет "1"
}

Для функции-расширения:

val String.lastChar: Char
    get() = this[length - 1]

fun main(args: Array<String>) {
    println(String::lastChar.get("abc")) // выведет "c"
}

Взаимодействие с рефлексией Java

На платформе Java стандартная библиотека Kotlin содержит расширения, которые сопоставляют расширяемые ими объекты рефлексии Kotlin с объектами рефлексии Java (см. пакет kotlin.reflect.jvm). К примеру, для нахождения поля или метода, который служит геттером для Kotlin-свойства, вы можете написать что-то вроде этого:

import kotlin.reflect.jvm.*
 
class A(val p: Int)
 
fun main(args: Array<String>) {
    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)

Привязанные функции

Вы можете сослаться на метод экземпляра конкретного объекта.

val numberRegex = "\\d+".toRegex()
println(numberRegex.matches("29")) // выведет "true"
 
val isNumber = numberRegex::matches
println(isNumber("29")) // выведет "true"

Вместо вызова метода matches напрямую, мы храним ссылку на него. Такие ссылки привязаны к объектам, к которым относятся:

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"