type | layout | title | url |
---|---|---|---|
doc |
reference |
Использование строителей с выводом типа строителя |
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
. Вывод строителя позволяет опускать
аргументы типа при использовании универсальных строителей.
До Kotlin 1.6.0 для включения вывода строителя для функции строителя требовалось аннотация
@BuilderInference
в лямбда-параметре строителя. В версии 1.6.0 вы можете её опустить, если и вы, и клиенты вашего строителя используете опцию компилятора-Xenable-builder-inference
.
Чтобы вывод строителя работал для вашего собственного строителя, убедитесь, что в его объявлении есть лямбда-параметр типа функции с получателем. Существует также два требования к типу приемника:
-
Он должен использовать тип аргументов, который должен определить вывод строителя. Например:
fun <V> buildList(builder: MutableList<V>.() -> Unit) { ... }
Обратите внимание, что передача типов параметра напрямую, например
fun <T> myBuilder(builder: T.() -> Unit)
, еще не поддерживается.
-
Он должен предоставлять public члены или расширения, которые содержат соответствующие типы параметра в своей сигнатуре. Например:
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 { // Тип itemHolder1 - это ItemHolder<String> addItem(s) } val itemHolder2 = itemHolderBuilder { // Тип itemHolder2 - это ItemHolder<String> addAllItems(listOf(s)) } val itemHolder3 = itemHolderBuilder { // Тип itemHolder3 - это 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 имеет типы Pair<List<Int>, Map<String, Int>> }
-
определение типа аргументов, типы параметров которых являются типами параметров лямбды или возвращаемых значений,
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 определен как Map<Long, String> 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 имеет выведенный тип List<String>
После того как переменная отложенного типа присваивается переменной типа String
, вывод строителя получает информацию
о том, что x
является подтипом String
. Это присваивание является последним оператором в лямбде строителя, поэтому
анализ вывода строителя заканчивается результатом вывода аргумента E
в String
.
Обратите внимание, что вы всегда можете вызвать функции equals()
, hashCode()
и toString()
с переменной отложенного
типа в качестве получателя.
Вывод строителя может собирать различную информацию о типе, которые влияют на результат анализа. Он рассматривает:
-
Вызов методов в приемнике лямбды, которые используют тип параметра;
val result = buildList { // Тип аргумента выводится в String на основе переданного аргумента "value" add("value") } // result имеет выведенный тип List<String>
-
Указание ожидаемого типа для вызовов, возвращающих тип параметра;
val result = buildList { // Тип аргумента выводится в Float на основе ожидаемого типа val x: Float = get(0) } // result имеет тип List<Float>
class 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 имеет тип Foo<CharSequence> }
-
Передачу типов переменных отложенного типа в методы, которые ожидают конкретных типов;
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 имеет тип List<Long> val result2 = buildList { val x = get(0) val isLong = x.isMoreThat3() // ... } // result2 имеет тип List<String> val result3 = buildList { takeListOfStrings(this) } // result3 имеет тип List<String> }
-
Получение вызываемой ссылки на член лямбда-получателя.
fun main() { val result = buildList { val x: KFunction1<Int, Float> = ::get } // result имеет тип List<Float> }
fun takeFunction(x: KFunction1<Int, Float>) { ... } fun main() { val result = buildList { takeFunction(::get) } // result имеет тип List<Float> }
В конце анализа вывод строителя рассматривает всю собранную информацию о типе и пытается объединить ее в результирующий тип. Смотрите пример.
val result = buildList { // Вывод переменной отложенного типа E
// Учитывание, что E - это Number или подтип Number
val n: Number? = getOrNull(0)
// Учитывание, что E - это Int или супертип Int
add(1)
// E выводится в Int
} // result имеет тип List<Int>
Результирующий тип является наиболее специфичным типом, который соответствует информации о типе, собранной в ходе анализа. Если данная информация о типе противоречива и не может быть объединена, компилятор сообщает об ошибке.
Обратите внимание, что компилятор 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()) // Несоответствие типа (требуется String, найдено CharSequence)
}
}
Здесь возникает несоответствие типов, поскольку ожидаемый тип map указан вне лямбды конструктора.
Компилятор анализирует все операторы внутри с фиксированным типом получателя Map<in String, String>
.