Summary
Kotlin offers a powerful collection framework that is explicitly divided into read-only and mutable types, which encourages safer and more maintainable code compared to Java. In Kotlin, the three core collection types are:
- List: An ordered collection that can contain duplicate elements.
- Set: A collection of unique elements where the order is generally not guaranteed.
- Map: A collection of key-value pairs, where keys are unique.
This article explains the structure of Kotlin collections, their differences, and how to use them effectively, including sample code and practical explanations.
List – Ordered and Indexable
A List is an ordered collection where the position of elements matters, and duplicates are allowed. Lists are similar to arrays, but they offer more built-in functionality.
Key Features:
- Elements are accessed by index (
list[0]
for the first element). - Duplicates are allowed.
- Maintains the insertion order.
Example:
fun main() {
val numbers = listOf(1, 2, 3, 2, 4)
println(numbers[0]) // Output: 1
println(numbers) // Output: [1, 2, 3, 2, 4]
}
MutableList:
If you need to add, remove, or update elements, use MutableList.
fun main() {
val mutableNumbers = mutableListOf(1, 2, 3)
mutableNumbers.add(4)
println(mutableNumbers) // Output: [1, 2, 3, 4]
}
Why Kotlin Separates List
and MutableList
(Unlike Java)
In Java, even when you declare a collection as List<String>
, the underlying collection can still be changed if you hold a reference to a modifiable list. This can lead to unintended side effects and bugs.
Kotlin solves this problem by:
- Providing read-only interfaces like
List
andSet
. - Forcing you to explicitly use
MutableList
orMutableSet
when you want to change a collection.
This separation:
- Improves code safety and predictability.
- Makes mutability a conscious decision.
- Encourages using
val
for collections to make them immutable, which leads to more stable code.
Reference: Qiita Article
Set – Unique Elements
A Set is a collection that automatically eliminates duplicates. The order is usually undefined, meaning you should not rely on element positions.
Key Features:
Only unique elements are allowed.
Order is not guaranteed.
Can contain null (but only one null).
fun main() {
val uniqueNumbers = setOf(1, 2, 3, 2, 4)
println(uniqueNumbers) // Output: [1, 2, 3, 4]
}
MutableSet:
To modify a set (add/remove elements), use MutableSet.
fun main() {
val mutableSet = mutableSetOf(1, 2, 3)
mutableSet.add(4)
mutableSet.add(2) // No effect, duplicates not allowed
println(mutableSet) // Output: [1, 2, 3, 4]
}
Map – Key-Value Pairs
A Map stores pairs of keys and values. Unlike List or Set, maps are not part of the Collection interface because they work with key-value structures.
Key Features:
Keys must be unique.
Values can repeat.
Useful for associating related data.
Example:
fun main() {
val employeeMap = mapOf(1 to "Alice", 2 to "Bob", 3 to "Charlie")
println(employeeMap[2]) // Output: Bob
}
MutableMap:
Use MutableMap if you need to add, remove, or update entries.
fun main() {
val mutableEmployeeMap = mutableMapOf(1 to "Alice", 2 to "Bob")
mutableEmployeeMap[3] = "Charlie"
println(mutableEmployeeMap) // Output: {1=Alice, 2=Bob, 3=Charlie}
}
Collection Operations
Sorting Collections
Example: Sorting, Reversing, Shuffling
fun main() {
val names = listOf("Charlie", "Alice", "Bob")
println(names.sorted()) // Output: [Alice, Bob, Charlie]
println(names.reversed()) // Output: [Bob, Alice, Charlie]
println(names.shuffled()) // Output: Random order
}
Custom Sorting with Comparator
data class Person(val name: String, val age: Int)
fun main() {
val people = listOf(Person("Alice", 30), Person("Bob", 25), Person("Charlie", 35))
val sortedByAge = people.sortedBy { it.age }
println(sortedByAge)
}
Mapping Collections (Transformations)
Example: Transforming List Elements
fun main() {
val numbers = listOf(1, 2, 3)
val doubled = numbers.map { it * 2 }
println(doubled) // Output: [2, 4, 6]
}
Example: Mapping Keys and Values in a Map
fun main() {
val employeeMap = mapOf(1 to "Alice", 2 to "Bob")
val upperCaseNames = employeeMap.mapValues { it.value.uppercase() }
println(upperCaseNames) // Output: {1=ALICE, 2=BOB}
}
Flattening Nested Collections
When you have collections inside collections (like List>), flattening makes them a single list.
fun main() {
val listOfSets = listOf(setOf(1, 2), setOf(3, 4))
val flatList = listOfSets.flatten()
println(flatList) // Output: [1, 2, 3, 4]
}
Filtering Collections
Basic Filtering
fun main() {
val numbers = listOf(1, 2, 3, 4, 5)
val evenNumbers = numbers.filter { it % 2 == 0 }
println(evenNumbers) // Output: [2, 4]
}
Partitioning (Filtering with Two Groups)
fun main() {
val numbers = listOf(1, 2, 3, 4, 5)
val (even, odd) = numbers.partition { it % 2 == 0 }
println(even) // Output: [2, 4]
println(odd) // Output: [1, 3, 5]
}
Conclusion
Kotlin's collection system is powerful, expressive, and safer than many other languages thanks to its strict separation of read-only and mutable types.
By understanding how Lists, Sets, and Maps work, and learning the built-in transformation and filtering functions, you can write concise and readable Kotlin code that fully leverages the power of collections.
Reference: kotlin document