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