Использование построителей с выводом типов в построителях
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.isMoreThan3() = 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.isMoreThan3() // ... } // 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–2026 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