In this article I would like to emphasise over a few options that we have when working with data structure and more precisely with collections in a mutable and immutable fashion. So let’s start with a few of them and see their mutability state.
But first, what makes a list mutable or immutable?
Immutable Collections
An immutable collection, as the name suggests, cannot be modified after it was created. This means:
- Fixed Size: Once initialized, you cannot add, remove, or update elements in the collection.
- Read-only: These collections provide read-only access to their elements. You can iterate over the elements, access them, but not add/replace or remove them.
- Safety: Immutability guarantees that the collection won’t change unexpectedly. This is especially valuable in multi-threaded environments where concurrent modifications could lead to errors.
- Kotlin Implementation: In Kotlin, immutable collections are created by default when you use functions like
listOf()
,setOf()
, andmapOf()
.
Mutable Collections
Mutable collections, on the other hand, are dynamic. They allow:
- Modification: You can add, remove, or update elements.
- Variable Size: The size of the collection can change over time as elements are added or removed.
- Kotlin Implementation: In Kotlin, mutable collections are explicitly created using functions like
mutableListOf()
,mutableSetOf()
, andmutableMapOf()
.
On the safety side, mutable collections are prone to issues if used without being mindful. That is because their size can change at any time and from lots of places in your code and it makes debugging issues harder.
Having this said, let’s dive in and see the options we have in Kotlin.
List (immutable)
First of all let’s initialize a List in Kotlin:
val list: List<String> = listOf("Apple", "Cherry", "Carrot")
Important!
In Kotlin, lists are immutable by default (you can’t add or remove elements after the list is created). If you need to make a list mutable, you can do this by using
MutableList
andmutableListOf
to quickly create a mutable list.
MutableList (mutable)
Bellow you can see mutableListOf
in action and then adding a new item to the mutable list. The example uses a mutable list of String in this case but obviously it can be used any type of class.
val list: List<String> = mutableListOf<>("Apple", "Cherry", "Carrot") list.add("Cucumber")
Additionally, to create a mutable list one can use an ArrayList like you see bellow.
val list: ArrayList<String> = arrayListOf("Apple", "Cherry", "Carrot") //mutable
Mixed types
When you create a list in Kotlin, you can add mixed class types to the same list:
val list = listOf("Apple", "Cherry", 100, true)
listOfNotNull() Function
val fruit = null val list = listOfNotNull("Apple", "Cherry", fruit) for (i in list) { println(i) }
The output of the above code will be this:
Apple
Cherry
Notice that null values are ignored. See also the Kotlin Functions article.
Empty lists
There are a few ways to create empty lists. They do the same thing, so it’s up to you which way you prefer. Of course they both create immutable lists.
val list = listOf<String>()
OR
val list = emptyList<String>()
Set (immutable)
If you need to create an unordered list in Kotlin having unique elements, you can use a Set. Like Lists, the Set is also a collection that can be used either as mutable or immutable structure depending on the implementation and the usage. Also, I recommend the same rule as for the lists, prefer immutable over mutable ones whenever is possible to avoid potential malicious or unintended usage.
Create a Set in Kotlin
val set = setOf("Apple", "Cherry", "Carrot") //immutable
HashSet in Kotlin (mutable)
val set = hashSetOf("Apple", "Cherry", "Carrot") //mutable
Create a SortedSet (TreeSet in Java – mutable)
val set = sortedSetOf("Apple", "Cherry", "Carrot") //mutable
Initialize a LinkedSet (LinkedHashSet in Java – mutable)
val set = linkedSetOf("Apple", "Cherry", "Carrot") //mutable
Map (immutable)
In Kotlin, as in many other programming languages, a Map is a fundamental data structure that is useful for a variety of reasons. Here’s a few of them:
- Key-Value Pair Storage: A Map stores data in key-value pairs, making it efficient to look up a value based on its key. This is particularly useful when you need to quickly retrieve data without searching through an entire list, as long as you have the key.
- Efficient Lookup: Retrieval of values based on keys is typically very fast in a map, especially in implementations like HashMap where average time complexity for many operations (like get and put) is O(1).
- Uniqueness of Keys: Each key in a map is unique. This characteristic makes maps ideal for cases where you need to prevent duplicate entries, like storing unique identifiers mapped to specific data.
Create a Map
val map = mapOf(1 to "John", 2 to "Chloe", 3 to "Maria") //immutable
How to iterate a Map?
val map = mapOf(1 to "John", 2 to "Chloe", 3 to "Maria") for ((key, value) in map) { println("$value has the id = $key") }
HashMap, LinkedMap (LinkedHashMap in Java) and SortedMap are all mutable.
Useful methods
any()
Returns true
if collection has at least one element.
val list = listOf("Apple", "Cherry", "Carrot") println("The list has at least one element = ${list.any()}") //returns TRUE
any(predicate: (T) -> Boolean
Returns true
if at least one element matches the given predicate.
val list = listOf("Apple", "Cherry", "Carrot") println("The list has at least one element of 15 letters = ${list.any{it.length == 15}}") //returns FALSE
asReversed()
Returns a reversed read-only view of the original List.
val list = listOf("Apple", "Cherry", "Carrot") println(list.asReversed())
Output
I/System.out: [Carrot, Cherry, Apple]
Maybe you are interested also in this article about common questions in Kotlin.