Kotlin provides three main collection types: List, Set, and Map. Choose a collection type based on how you need to store and access data.
List
List<T> stores elements in a specified order and provides indexed access to them. Indices start from zero and end at lastIndex, which is (list.size - 1). A list can contain duplicate elements, including multiple references to the same object.
fun main() {
//sampleStart
val numbers = listOf("one", "two", "three", "four")
println("Number of elements: ${numbers.size}")
// Number of elements: 4
println("Third element: ${numbers.get(2)}")
// Third element: three
println("Fourth element: ${numbers[3]}")
// Fourth element: four
println("Index of element \"two\": ${numbers.indexOf("two")}")
// Index of element "two": 1
//sampleEnd
}
Two lists are equal if they have the same size and structurally equal elements at the same positions:
data class Person(var name: String, var age: Int)
fun main() {
//sampleStart
val bob = Person("Bob", 31)
val people = listOf(Person("Adam", 20), bob, bob)
val people2 = listOf(Person("Adam", 20), Person("Bob", 31), bob)
println(people == people2)
// true
bob.age = 32
println(people == people2)
// false
//sampleEnd
}
MutableList<T> is a List with list-specific write operations. Unlike an array, a mutable list can change size when you add or remove elements. You can also replace an element at a specific position. For example:
fun main() {
//sampleStart
val numbers = mutableListOf(1, 2, 3, 4)
// Adds an element to the list
numbers.add(5)
// Removes the element at index 1
numbers.removeAt(1)
// Replaces the element at index 0
numbers[0] = 0
numbers.shuffle()
println(numbers)
//sampleEnd
}
The default implementation of MutableList is ArrayList.
Set
Set<T> stores unique elements. The Set<T> interface doesn't guarantee iteration order. The order depends on the implementation. A set can contain at most one null element if its element type is nullable and its implementation supports null.
Two sets are equal if they have the same size and each element of one set has an equal element in the other set:
fun main() {
//sampleStart
val numbers = setOf(1, 2, 3, 4)
println("Number of elements: ${numbers.size}")
// Number of elements: 4
if (numbers.contains(1)) println("1 is in the set")
// 1 is in the set
val numbersBackwards = setOf(4, 3, 2, 1)
println("The sets are equal: ${numbers == numbersBackwards}")
// The sets are equal: true
//sampleEnd
}
MutableSet<T> is a Set with write operations from MutableCollection.
LinkedHashSet is the default implementation of MutableSet. It preserves the element insertion order. Therefore, order-dependent functions like first() and last() return predictable results:
fun main() {
//sampleStart
// LinkedHashSet is the default implementation
val numbers = setOf(1, 2, 3, 4)
val numbersBackwards = setOf(4, 3, 2, 1)
println(numbers.first() == numbersBackwards.first())
// false
println(numbers.first() == numbersBackwards.last())
// true
//sampleEnd
}
If you don't need a predictable iteration order, you can use an alternative implementation HashSet, which requires less memory than LinkedHashSet. Since it doesn't guarantee iteration order, you can't rely on first() or last() returning elements in insertion order.
Map
Even though Map<K, V> is not an inheritor of the Collection interface, it's a Kotlin collection type. A Map stores key-value pairs or entries. Keys are unique, but different keys can be paired with equal values. The Map interface provides specific functions, such as accessing values by key, searching for keys and values, and so on.
fun main() {
//sampleStart
val numbersMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key4" to 1)
println("All keys: ${numbersMap.keys}")
// All keys: [key1, key2, key3, key4]
println("All values: ${numbersMap.values}")
// All values: [1, 2, 3, 1]
if ("key2" in numbersMap) println("Value by key \"key2\": ${numbersMap["key2"]}")
// Value by key "key2": 2
if (1 in numbersMap.values) println("The value 1 is in the map")
// The value 1 is in the map
// Checks for the value with containsValue(), equivalent to the previous condition
if (numbersMap.containsValue(1)) println("The value 1 is in the map")
// The value 1 is in the map
//sampleEnd
}
A map can contain at most one null key if its key type is nullable and its implementation supports null keys. Support for null values also depends on the value type and the implementation.
Two maps are equal if they contain the same key-value pairs, regardless of the order:
fun main() {
//sampleStart
val numbersMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key4" to 1)
val anotherMap = mapOf("key2" to 2, "key1" to 1, "key4" to 1, "key3" to 3)
println("The maps are equal: ${numbersMap == anotherMap}")
// The maps are equal: true
//sampleEnd
}
MutableMap<K, V> is a Map with map-specific write operations. For example, you can add a new key-value pair or update the value associated with the given key:
fun main() {
//sampleStart
val numbersMap = mutableMapOf("one" to 1, "two" to 2)
numbersMap.put("three", 3)
numbersMap["one"] = 11
println(numbersMap)
// {one=11, two=2, three=3}
//sampleEnd
}
LinkedHashMap is the default implementation of MutableMap. It preserves the order in which you insert the entries. In turn, HashMap is an alternative implementation that doesn't guarantee the order of its entries.
ArrayDeque
ArrayDeque<T> is an implementation of a double-ended queue. It allows you to add or remove elements at either the beginning or the end of the deque. ArrayDeque also fills the role of both a stack and a queue data structure in Kotlin. ArrayDeque stores its elements in a resizable array: