Использование билдеров с выводом типа билдера
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–2023 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