Kotlin Collections Overview
Collections are groups of values with the same type that are treated as a single unit. Kotlin provides a rich collection framework that is both powerful and easy to use, taking inspiration from functional programming while maintaining compatibility with Java collections.
Introduction to Kotlin Collections
In Kotlin, collections are fundamental data structures that allow you to store, retrieve, manipulate, and pass around groups of related values. Understanding collections is crucial for effective programming as they form the backbone of data manipulation in most applications.
Kotlin's collection framework has two main characteristics:
- Mutable vs Immutable Collections: Kotlin clearly distinguishes between read-only and mutable collections
- Rich API: Kotlin provides extensive functions to transform and manipulate collections efficiently
Collection Types
Kotlin supports the following main collection types:
Arrays
Arrays in Kotlin are represented by the Array
class, which is a fixed-size collection that stores values of the same type.
// Creating arrays
val numbers = arrayOf(1, 2, 3, 4, 5)
val zeros = IntArray(5) // Creates [0, 0, 0, 0, 0]
val squares = Array(5) { i -> i * i } // Creates [0, 1, 4, 9, 16]
// Accessing elements
println(numbers[0]) // Output: 1
// Modifying elements
numbers[0] = 10
println(numbers[0]) // Output: 10
// Array properties
println("Size: ${numbers.size}") // Output: Size: 5
Lists
Lists are ordered collections where elements can be accessed by indices. Kotlin provides both read-only (List
) and mutable (MutableList
) versions.
// Read-only list
val readOnlyList = listOf("Apple", "Banana", "Cherry")
// Mutable list
val mutableList = mutableListOf("Apple", "Banana", "Cherry")
mutableList.add("Date")
mutableList.removeAt(0)
println(readOnlyList) // Output: [Apple, Banana, Cherry]
println(mutableList) // Output: [Banana, Cherry, Date]
// Common operations
println("First element: ${readOnlyList.first()}") // Output: First element: Apple
println("Last element: ${readOnlyList.last()}") // Output: Last element: Cherry
println("List size: ${readOnlyList.size}") // Output: List size: 3
println("Contains Banana? ${readOnlyList.contains("Banana")}") // Output: Contains Banana? true
Sets
Sets are collections of unique elements. Like lists, Kotlin provides both read-only (Set
) and mutable (MutableSet
) versions.
// Read-only set
val readOnlySet = setOf("Apple", "Banana", "Cherry", "Apple") // Note the duplicate "Apple"
// Mutable set
val mutableSet = mutableSetOf("Apple", "Banana", "Cherry")
mutableSet.add("Date")
mutableSet.remove("Apple")
println(readOnlySet) // Output: [Apple, Banana, Cherry] - duplicates are removed
println(mutableSet) // Output: [Banana, Cherry, Date]
// Common operations
println("Set size: ${readOnlySet.size}") // Output: Set size: 3
println("Contains Cherry? ${readOnlySet.contains("Cherry")}") // Output: Contains Cherry? true
Maps
Maps store key-value pairs. Kotlin provides both read-only (Map
) and mutable (MutableMap
) versions.
// Read-only map
val readOnlyMap = mapOf(
"apple" to 100,
"banana" to 150,
"cherry" to 200
)
// Mutable map
val mutableMap = mutableMapOf(
"apple" to 100,
"banana" to 150,
"cherry" to 200
)
mutableMap["date"] = 250
mutableMap.remove("apple")
println(readOnlyMap) // Output: {apple=100, banana=150, cherry=200}
println(mutableMap) // Output: {banana=150, cherry=200, date=250}
// Accessing values
println("Price of banana: ${readOnlyMap["banana"]}") // Output: Price of banana: 150
// Common operations
println("Map size: ${readOnlyMap.size}") // Output: Map size: 3
println("Contains key 'apple'? ${readOnlyMap.containsKey("apple")}") // Output: Contains key 'apple'? true
println("Contains value 200? ${readOnlyMap.containsValue(200)}") // Output: Contains value 200? true
Mutable vs Immutable Collections
One of Kotlin's key features is the clear distinction between mutable and immutable collections:
Immutable (Read-only) | Mutable |
---|---|
List<T> | MutableList<T> |
Set<T> | MutableSet<T> |
Map<K, V> | MutableMap<K, V> |
// Read-only (immutable) collections
val immutableList = listOf(1, 2, 3)
val immutableSet = setOf(1, 2, 3)
val immutableMap = mapOf("a" to 1, "b" to 2)
// Mutable collections
val mutableList = mutableListOf(1, 2, 3)
val mutableSet = mutableSetOf(1, 2, 3)
val mutableMap = mutableMapOf("a" to 1, "b" to 2)
// This works
mutableList.add(4)
// This would cause a compilation error
// immutableList.add(4)
Collection Constructors
Kotlin provides several ways to create collections:
// Empty collections
val emptyList = emptyList<String>()
val emptySet = emptySet<Int>()
val emptyMap = emptyMap<String, Int>()
// Collections with elements
val list = listOf(1, 2, 3)
val set = setOf("a", "b", "c")
val map = mapOf(1 to "one", 2 to "two")
// Mutable collections
val mutableList = mutableListOf<String>()
mutableList.add("Hello")
val mutableSet = mutableSetOf<Int>()
mutableSet.add(42)
val mutableMap = mutableMapOf<String, Int>()
mutableMap["answer"] = 42
Practical Examples
Example 1: Managing a To-Do List
fun main() {
val todoList = mutableListOf(
"Buy groceries",
"Pay bills",
"Call mom",
"Finish Kotlin tutorial"
)
// Display current tasks
println("Current tasks:")
todoList.forEachIndexed { index, task ->
println("${index + 1}. $task")
}
// Mark a task as completed (remove it)
val completedTask = todoList.removeAt(0)
println("\nCompleted: $completedTask")
// Add new task
todoList.add("Go to the gym")
// Display updated list
println("\nUpdated tasks:")
todoList.forEachIndexed { index, task ->
println("${index + 1}. $task")
}
}
Output:
Current tasks:
1. Buy groceries
2. Pay bills
3. Call mom
4. Finish Kotlin tutorial
Completed: Buy groceries
Updated tasks:
1. Pay bills
2. Call mom
3. Finish Kotlin tutorial
4. Go to the gym
Example 2: Frequency Analysis of Words
fun main() {
val text = """
Kotlin is a modern programming language.
Kotlin runs on the JVM.
Kotlin is concise and expressive.
""".trimIndent()
// Split into words and convert to lowercase
val words = text.lowercase().split(Regex("\\s+|\\.|,"))
.filter { it.isNotEmpty() }
// Count word frequencies
val wordFrequency = mutableMapOf<String, Int>()
for (word in words) {
wordFrequency[word] = wordFrequency.getOrDefault(word, 0) + 1
}
// Display results
println("Word Frequency Analysis:")
wordFrequency.entries
.sortedByDescending { it.value }
.forEach { (word, count) ->
println("$word: $count")
}
}
Output:
Word Frequency Analysis:
kotlin: 3
is: 2
a: 1
modern: 1
programming: 1
language: 1
runs: 1
on: 1
the: 1
jvm: 1
concise: 1
and: 1
expressive: 1
Example 3: Student Grade Tracker
data class Student(val name: String, val id: Int)
fun main() {
// Map of students and their grades
val grades = mutableMapOf<Student, MutableList<Int>>()
// Add students and grades
val alice = Student("Alice", 101)
val bob = Student("Bob", 102)
val charlie = Student("Charlie", 103)
grades[alice] = mutableListOf(85, 90, 78, 92)
grades[bob] = mutableListOf(76, 88, 90, 84)
grades[charlie] = mutableListOf(92, 93, 88, 95)
// Calculate average grade for each student
val averages = grades.mapValues { (_, gradeList) ->
gradeList.average()
}
// Print results
println("Student Grade Averages:")
averages.forEach { (student, average) ->
println("${student.name} (ID: ${student.id}): ${"%.2f".format(average)}")
}
// Find highest achieving student
val topStudent = averages.maxByOrNull { it.value }
topStudent?.let {
println("\nHighest achieving student: ${it.key.name} with average ${it.value}")
}
}
Output:
Student Grade Averages:
Alice (ID: 101): 86.25
Bob (ID: 102): 84.50
Charlie (ID: 103): 92.00
Highest achieving student: Charlie with average 92.0
Collection Operations
Kotlin collections come with a rich set of operations influenced by functional programming:
Transformation Operations
val numbers = listOf(1, 2, 3, 4, 5)
// map - transform each element
val doubled = numbers.map { it * 2 }
println(doubled) // Output: [2, 4, 6, 8, 10]
// filter - keep elements matching predicate
val evens = numbers.filter { it % 2 == 0 }
println(evens) // Output: [2, 4]
// flatMap - map each element to a collection and flatten the results
val nested = listOf(listOf(1, 2, 3), listOf(4, 5, 6))
val flattened = nested.flatMap { it }
println(flattened) // Output: [1, 2, 3, 4, 5, 6]
Grouping Operations
val words = listOf("apple", "banana", "cherry", "avocado", "blueberry")
// Group by first letter
val grouped = words.groupBy { it.first() }
println(grouped)
// Output: {a=[apple, avocado], b=[banana, blueberry], c=[cherry]}
// Partition into two groups
val (shortWords, longWords) = words.partition { it.length <= 5 }
println("Short words: $shortWords") // Output: Short words: [apple]
println("Long words: $longWords") // Output: Long words: [banana, cherry, avocado, blueberry]
Aggregation Operations
val numbers = listOf(1, 2, 3, 4, 5)
// Sum
println(numbers.sum()) // Output: 15
// Average
println(numbers.average()) // Output: 3.0
// Min and Max
println(numbers.minOrNull()) // Output: 1
println(numbers.maxOrNull()) // Output: 5
// Reduce - combine elements into a single value
val product = numbers.reduce { acc, i -> acc * i }
println(product) // Output: 120 (1*2*3*4*5)
// Fold - like reduce but with an initial value
val sumPlusTen = numbers.fold(10) { acc, i -> acc + i }
println(sumPlusTen) // Output: 25 (10+1+2+3+4+5)
Summary
Kotlin collections provide a powerful and flexible way to work with groups of data. The key points to remember:
- Kotlin provides both mutable and immutable (read-only) collections
- Main collection types are Lists, Sets, Maps, and Arrays
- Collections come with a rich API influenced by functional programming
- Kotlin collections are fully interoperable with Java collection classes
Mastering collections is essential for effective Kotlin programming, as they enable you to handle complex data manipulations with clear and concise code.
Exercises
- Create a program that takes a list of numbers and returns a new list containing only the even numbers, doubled.
- Write a function that takes a list of strings and returns a map where the keys are the lengths of the strings and the values are lists of strings with that length.
- Create a program that manages a shopping cart using a mutable map, allowing items to be added, removed, and quantities to be updated.
- Write a function that takes two lists and returns a set containing only elements that appear in both lists.
- Create a program that reads a text file and counts the frequency of each word, ignoring case.
Additional Resources
- Kotlin Official Documentation on Collections
- Learn Kotlin by Example: Collections
- Kotlin for Java Developers: Collections on Coursera
- Effective Kotlin: Item 30 - Consider Using Immutable Collections
Happy coding with Kotlin collections!
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)