Kotlin’s Builder Functions: A Better Way to Create Lists, Maps, Strings & Sets
Kotlin offers several convenience functions to create lists, maps, strings, and more without the usual boilerplate code.
Kotlin offers several convenience functions to create lists, maps, strings, and more without the usual boilerplate code.
In this short post, we’ll examine a few common functions in the Kotlin standard library that make constructing those objects easier.
Building a List
The usual approaches to creating and populating a dynamic list are, for example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
val newList = mutableListOf<Int>()
newList.add(1)
if (conditionFullfilled) {
newList.add(2)
}
// OR
val newList = mutableListOf<Int>().apply {
add(1)
if (conditionFullfilled) {
add(2)
}
}
We can make that easier by using the buildList {}
function that creates a mutable list under the hood, allows us to call functions on it, and then returns an immutable list.
1
2
3
4
5
6
7
val newList = buildList {
add(1)
if (conditionFullfilled) {
add(2)
}
}
Looking at the implementation of the buildList()
function, we can see that it works similarly to our initial code. It calls the buildListInternal()
function that constructs a new mutable list and applies the actions to it. And since it’s an inline
function, it also means that the overhead of the additional lambda is removed, since it will copy the code to the call site.
1
2
3
4
5
6
7
8
@SinceKotlin("1.6")
@WasExperimental(ExperimentalStdlibApi::class)
@kotlin.internal.InlineOnly
@Suppress("LEAKED_IN_PLACE_LAMBDA", "WRONG_INVOCATION_KIND")
public inline fun <E> buildList(@BuilderInference builderAction: MutableList<E>.() -> Unit): List<E> {
contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
return buildListInternal(builderAction)
}
buildList()
can be used for any type, including primitives like Int
, Float
, Double
, and Long
. However, if we want to avoid auto-boxing when storing and retrieving the elements, we can use dedicated functions:
buildIntList {}
: constructs aMutableIntList
and returns aIntList
,buildLongList {}
: constructs aMutableLongList
and returns aLongList
,buildFloatList {}
: constructs aMutableFloatList
and returns aFloatList
,buildDoubleList {}
: constructs aMutableDoubleList
and returns aDoubleList
.
Building a String
A typical way of constructing a string that requires concatenation based on some condition is to create a StringBuilder
object and then call the append
functions on it and convert it to a string.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
val sb = StringBuilder()
sb.append("Some")
if (conditionFullfilled) {
sb.append(" string")
}
val newString = sb.toString()
// OR
val newString = StringBuilder().apply {
append("Some")
if (conditionFullfilled) {
append(" string")
}
}.toString()
We can make that easier by using the buildString {}
function that creates the StringBuilder
object for us and returns the output string. We can call the usual append
functions on it inside the code block.
1
2
3
4
5
6
7
val newString = buildString {
append("Some")
if (conditionFullfilled) {
append(" string")
}
}
Looking at the implementation of the buildString()
function, we can see that it works the same as our initial code. And since it’s an inline
function, it also means that the overhead of the additional lambda is removed, since it will copy the code to the call site.
1
2
3
4
5
@kotlin.internal.InlineOnly
public inline fun buildString(builderAction: StringBuilder.() -> Unit): String {
contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
return StringBuilder().apply(builderAction).toString()
}
Building a Set
Similar to building a list and string, there is a helper function for building a set. It constructs a mutable set, allows us to call functions on it, and then returns an immutable version of the set that preserves the insertion order.
1
2
3
4
5
6
7
val newSet = buildSet {
add(1)
if (conditionFullfilled) {
addAll(someOtherSet)
}
}
The buildSet()
function can be used for any type. However, there are also other versions of the function for building type-specific sets:
buildIntSet {}
: constructs aMutableIntSet
and returns aIntSet
,buildFloatSet {}
: constructs aMutableFloatSet
and returns aFloatSet
,buildLongSet {}
: constructs aMutableLongSet
and returns aLongSet
.
The three type-bound sets use a flat hash table underneath and don’t preserve insertion order.
Building a Map
We can build a new map using the buildMap {}
function. It constructs a new MutableMap
underneath, allows us to call functions on it, and then returns an immutable Map
.
1
2
3
4
5
6
7
val newMap = buildMap {
put("key", "value")
if (conditionFullfilled) {
putAll(someOtherMap)
}
}
If our keys or values are primitives like Int
, Float
, Double
, or Long
, we can use optimized variants of the function:
buildIntIntMap {}
: constructs aMutableIntIntMap
and returns aIntIntMap
, where the keys and values areInt
primitives,buildLongLongMap {}
: constructs aMutableLongLongMap
and returns aLongLongMap
, where the keys and values areLong
primitives,buildFloatFloatMap {}
: constructs aMutableFloatFloatMap
and returns aFloatFloatMap
, where the keys and values areFloat
primitives,buildObjectObjectMap {}
: constructs aMutableObjectObjectMap
and returns aObjectObjectMap
, where the keys and values are reference types.
There are even more variants of this function for different combinations of key and value types: buildIntFloatMap {}
, buildIntLongMap {}
, buildObjectFloatMap {}
and so on.
Other builders
The above functions are all part of the Kotlin standard library and should be available in most projects. There are other similar builder functions declared in other dependencies, for example: buildSpannedString {}
and buildAnnotatedString {}
that are part of Compose, and buildJsonObject {}
that is part of the Kotlinx Serialization library.
Conclusion
The Kotlin Standard Library offers several convenience functions for building common types like lists, sets, maps, and strings. They provide a standardized way of constructing those types and avoid common boilerplate.
I hope you found this useful and learned something new. Let me know what other builder functions you’re using.
Consider following me on Medium to get latest articles as they come out: https://medium.com/@domen.lanisnik