Использование билдеров с выводом типа билдера
Kotlin поддерживает вывод типов билдера (или вывод билдера), что может быть полезно при работе с универсальными билдерами. Это помогает компилятору вывести аргументы типа вызова билдера на основе информации о типе других вызовов внутри его лямбда-аргумента.
Рассмотрим этот пример использования buildMap():
fun addEntryToMap(baseMap: Map<String, Number>, additionalEntry: Pair<String, Int>?) {
val myMap = buildMap {
putAll(baseMap)
if (additionalEntry != null) {
put(additionalEntry.first, additionalEntry.second)
}
}
}
Здесь недостаточно информации о типе для вывода аргументов типа обычным способом, но вывод типов билдера может проанализировать вызовы внутри лямбда-аргумента. Основываясь на информации о типах вызовов putAll() и put(), компилятор может автоматически вывести аргументы типа вызова buildMap() в String и Number. Вывод типов билдера позволяет опустить аргументы типа при использовании универсальных билдеров.
Создание собственных билдеров
Требования для включения вывода типов билдера
Чтобы вывод типов билдера работал для вашего собственного билдера, убедитесь, что его объявление имеет лямбда-параметр билдера типа функции с получателем. Существует также два требования к типу получателя:
-
Он должен использовать аргументы типа, которые должен вывести вывод типов билдера. Например:
fun <V> buildList(builder: MutableList<V>.() -> Unit) { ... } -
Он должен предоставлять публичные члены или расширения, которые содержат соответствующие параметры типа в своей сигнатуре. Например:
class ItemHolder<T> { private val items = mutableListOf<T>() fun addItem(x: T) { items.add(x) } fun getLastItem(): T? = items.lastOrNull() } fun <T> ItemHolder<T>.addAllItems(xs: List<T>) { xs.forEach { addItem(it) } } fun <T> itemHolderBuilder(builder: ItemHolder<T>.() -> Unit): ItemHolder<T> = ItemHolder<T>().apply(builder) fun test(s: String) { val itemHolder1 = itemHolderBuilder { // Type of itemHolder1 is ItemHolder<String> addItem(s) } val itemHolder2 = itemHolderBuilder { // Type of itemHolder2 is ItemHolder<String> addAllItems(listOf(s)) } val itemHolder3 = itemHolderBuilder { // Type of itemHolder3 is ItemHolder<String?> val lastItem: String? = getLastItem() // ... } }
Поддерживаемые функции
Вывод типов билдера поддерживает:
-
Вывод нескольких аргументов типа
fun <K, V> myBuilder(builder: MutableMap<K, V>.() -> Unit): Map<K, V> { ... } -
Вывод аргументов типа нескольких лямбда-билдеров в одном вызове, включая взаимозависимые
fun <K, V> myBuilder( listBuilder: MutableList<V>.() -> Unit, mapBuilder: MutableMap<K, V>.() -> Unit ): Pair<List<V>, Map<K, V>> = mutableListOf<V>().apply(listBuilder) to mutableMapOf<K, V>().apply(mapBuilder) fun main() { val result = myBuilder( { add(1) }, { put("key", 2) } ) // result has Pair<List<Int>, Map<String, Int>> type } -
Вывод аргументов типа, чьи параметры типа являются параметрами или возвращаемыми типами лямбды
fun <K, V> myBuilder1( mapBuilder: MutableMap<K, V>.() -> K ): Map<K, V> = mutableMapOf<K, V>().apply { mapBuilder() } fun <K, V> myBuilder2( mapBuilder: MutableMap<K, V>.(K) -> Unit ): Map<K, V> = mutableMapOf<K, V>().apply { mapBuilder(2 as K) } fun main() { // result1 has the Map<Long, String> type inferred val result1 = myBuilder1 { put(1L, "value") 2 } val result2 = myBuilder2 { put(1, "value 1") // You can use `it` as "postponed type variable" type // See the details in the section below put(it, "value 2") } }
Как работает вывод типов билдера
Отложенные переменные типов
Вывод типов билдера работает с помощью отложенных переменных типов, которые появляются внутри лямбды билдера во время анализа вывода типов билдера. Отложенная переменная типа — это тип аргумента типа, который в процессе вывода. Компилятор использует его для сбора информации о типе аргумента.
Рассмотрим пример с buildList():
val result = buildList {
val x = get(0)
}
Здесь x имеет тип отложенной переменной типа: вызов get() возвращает значение типа E, но сам E еще не определен. В данный момент конкретный тип для E неизвестен.
Когда значение отложенной переменной типа связывается с конкретным типом, вывод типов билдера собирает эту информацию, чтобы вывести результирующий тип соответствующего аргумента типа в конце анализа вывода типов билдера. Например:
val result = buildList {
val x = get(0)
val y: String = x
} // result has the List<String> type inferred
После того как отложенная переменная типа присваивается переменной типа String, вывод типов билдера получает информацию о том, что x является подтипом String. Это присвоение — последнее утверждение в лямбда-билдере, поэтому анализ вывода типов билдера завершается результатом вывода аргумента типа E в String.
Обратите внимание, что вы всегда можете вызвать функции equals(), hashCode() и toString() с отложенной переменной типа в качестве получателя.
Вклад в результаты вывода типов билдера
Вывод типов билдера может собирать различные типы информации, которые вносят вклад в результат анализа. Он учитывает:
-
Вызов методов на получателе лямбды, которые используют тип параметра
val result = buildList { // Type argument is inferred into String based on the passed "value" argument add("value") } // result has the List<String> type inferred -
Указание ожидаемого типа для вызовов, которые возвращают тип параметра
val result = buildList { // Type argument is inferred into Float based on the expected type val x: Float = get(0) } // result has the List<Float> typeclass Foo<T> { val items = mutableListOf<T>() } fun <K> myBuilder(builder: Foo<K>.() -> Unit): Foo<K> = Foo<K>().apply(builder) fun main() { val result = myBuilder { val x: List<CharSequence> = items // ... } // result has the Foo<CharSequence> type } -
Передача типов отложенных переменных типа в методы, которые ожидают конкретные типы
fun takeMyLong(x: Long) { ... } fun String.isMoreThat3() = length > 3 fun takeListOfStrings(x: List<String>) { ... } fun main() { val result1 = buildList { val x = get(0) takeMyLong(x) } // result1 has the List<Long> type val result2 = buildList { val x = get(0) val isLong = x.isMoreThat3() // ... } // result2 has the List<String> type val result3 = buildList { takeListOfStrings(this) } // result3 has the List<String> type } -
Получение ссылки на вызов члена получателя лямбды
fun main() { val result = buildList { val x: KFunction1<Int, Float> = ::get } // result has the List<Float> type }fun takeFunction(x: KFunction1<Int, Float>) { ... } fun main() { val result = buildList { takeFunction(::get) } // result has the List<Float> type }
В конце анализа вывод типов билдера учитывает всю собранную информацию о типе и пытается объединить ее в результирующий тип. См. пример.
val result = buildList { // Inferring postponed type variable E
// Considering E is Number or a subtype of Number
val n: Number? = getOrNull(0)
// Considering E is Int or a supertype of Int
add(1)
// E gets inferred into Int
} // result has the List<Int> type
Результирующий тип — это наиболее конкретный тип, соответствующий информации о типе, собранной во время анализа. Если предоставленная информация о типе противоречива и не может быть объединена, компилятор сообщает об ошибке.
Обратите внимание, что компилятор Kotlin использует вывод типов билдера только в том случае, если обычный вывод типов не может вывести аргумент типа. Это означает, что вы можете предоставить информацию о типе за пределами лямбды билдера, и тогда анализ вывода типов билдера не требуется. Рассмотрим пример:
fun someMap() = mutableMapOf<CharSequence, String>()
fun <E> MutableMap<E, String>.f(x: MutableMap<E, String>) { ... }
fun main() {
val x: Map<in String, String> = buildMap {
put("", "")
f(someMap()) // Type mismatch (required String, found CharSequence)
}
}
Здесь появляется несовпадение типов, потому что ожидаемый тип карты указан за пределами лямбды билдера. Компилятор анализирует все утверждения внутри с фиксированным типом получателя Map<in String, String>.
© 2010–2022 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/using-builders-with-builder-inference.html