Arrays and Collections
Overview
Kotlin provides rich array and collection types for storing and manipulating data. This chapter will detail arrays, lists, sets, maps, and other data structures, as well as their creation, operations, and best practices.
Arrays
Basic Array Operations
kotlin
fun main() {
// Different ways to create arrays
val numbers1 = arrayOf(1, 2, 3, 4, 5)
val numbers2 = Array(5) { i -> i * 2 } // [0, 2, 4, 6, 8]
val numbers3 = Array(5) { 0 } // [0, 0, 0, 0, 0]
// Arrays with specified type
val strings: Array<String> = arrayOf("apple", "banana", "cherry")
val nullableStrings: Array<String?> = arrayOfNulls(3)
println("Array creation:")
println("numbers1: ${numbers1.contentToString()}")
println("numbers2: ${numbers2.contentToString()}")
println("strings: ${strings.contentToString()}")
// Array access and modification
println("\nArray access:")
println("First element: ${numbers1[0]}")
println("Last element: ${numbers1[numbers1.size - 1]}")
println("Array length: ${numbers1.size}")
// Modify array element
numbers1[0] = 10
println("After modification: ${numbers1.contentToString()}")
// Safe access
fun safeGet(array: Array<Int>, index: Int): Int? {
return if (index in array.indices) array[index] else null
}
println("Safe access index 5: ${safeGet(numbers1, 5)}")
println("Safe access index 2: ${safeGet(numbers1, 2)}")
}Primitive Type Arrays
kotlin
fun main() {
// Primitive type arrays (avoid boxing overhead)
val intArray = intArrayOf(1, 2, 3, 4, 5)
val doubleArray = doubleArrayOf(1.1, 2.2, 3.3)
val booleanArray = booleanArrayOf(true, false, true)
val charArray = charArrayOf('a', 'b', 'c')
val longArray = LongArray(5) { it.toLong() * 10 }
println("Primitive type arrays:")
println("IntArray: ${intArray.contentToString()}")
println("DoubleArray: ${doubleArray.contentToString()}")
println("BooleanArray: ${booleanArray.contentToString()}")
println("CharArray: ${charArray.contentToString()}")
println("LongArray: ${longArray.contentToString()}")
// Array operations
println("\nArray operations:")
println("Sum: ${intArray.sum()}")
println("Average: ${intArray.average()}")
println("Maximum: ${intArray.maxOrNull()}")
println("Minimum: ${intArray.minOrNull()}")
println("Sorted: ${intArray.sorted()}")
// Array transformations
val doubled = intArray.map { it * 2 }
val filtered = intArray.filter { it > 2 }
val exists = intArray.any { it > 4 }
val allPositive = intArray.all { it > 0 }
println("Doubled: $doubled")
println("Filtered > 2: $filtered")
println("Exists > 4: $exists")
println("All positive: $allPositive")
}Multidimensional Arrays
kotlin
fun main() {
// 2D array
val matrix = Array(3) { Array(3) { 0 } }
val matrix2 = arrayOf(
arrayOf(1, 2, 3),
arrayOf(4, 5, 6),
arrayOf(7, 8, 9)
)
// Initialize matrix
for (i in matrix.indices) {
for (j in matrix[i].indices) {
matrix[i][j] = i * 3 + j + 1
}
}
println("Matrix operations:")
println("Matrix 1:")
printMatrix(matrix)
println("Matrix 2:")
printMatrix(matrix2)
// Matrix operations
val sum = addMatrices(matrix, matrix2)
println("Matrix addition:")
printMatrix(sum)
// Transpose matrix
val transposed = transposeMatrix(matrix2)
println("Transposed matrix:")
printMatrix(transposed)
}
fun printMatrix(matrix: Array<Array<Int>>) {
for (row in matrix) {
println(row.joinToString(" ") { "%3d".format(it) })
}
}
fun addMatrices(a: Array<Array<Int>>, b: Array<Array<Int>>): Array<Array<Int>> {
require(a.size == b.size && a[0].size == b[0].size) { "Matrix dimensions don't match" }
return Array(a.size) { i ->
Array(a[i].size) { j ->
a[i][j] + b[i][j]
}
}
}
fun transposeMatrix(matrix: Array<Array<Int>>): Array<Array<Int>> {
val rows = matrix.size
val cols = matrix[0].size
return Array(cols) { j ->
Array(rows) { i ->
matrix[i][j]
}
}
}Lists
Immutable Lists
kotlin
fun main() {
// Create immutable lists
val fruits = listOf("apple", "banana", "cherry", "date")
val numbers = listOf(1, 2, 3, 4, 5)
val mixed = listOf("hello", 42, 3.14, true)
val emptyList = emptyList<String>()
println("List creation:")
println("Fruits: $fruits")
println("Numbers: $numbers")
println("Mixed: $mixed")
// List access
println("\nList access:")
println("First fruit: ${fruits[0]}")
println("First fruit (first): ${fruits.first()}")
println("Last fruit: ${fruits.last()}")
println("Second fruit (getOrNull): ${fruits.getOrNull(1)}")
println("Index 10 (getOrNull): ${fruits.getOrNull(10)}")
// List properties
println("\nList properties:")
println("Size: ${fruits.size}")
println("Is empty: ${fruits.isEmpty()}")
println("Contains 'apple': ${"apple" in fruits}")
println("Contains 'grape': ${"grape" in fruits}")
// List slicing
println("\nList slicing:")
println("First 3: ${fruits.take(3)}")
println("Skip 2: ${fruits.drop(2)}")
println("Sublist [1,3): ${fruits.subList(1, 3)}")
// List searching
println("\nList searching:")
println("Index of 'cherry': ${fruits.indexOf("cherry")}")
println("Index of 'grape': ${fruits.indexOf("grape")}") // -1 means not found
println("First starting with 'a': ${fruits.find { it.startsWith('a') }}")
println("First with length > 5: ${fruits.find { it.length > 5 }}")
}Mutable Lists
kotlin
fun main() {
// Create mutable lists
val mutableFruits = mutableListOf("apple", "banana")
val arrayList = arrayListOf<String>()
println("Mutable list operations:")
println("Initial list: $mutableFruits")
// Add elements
mutableFruits.add("cherry")
mutableFruits.add(1, "orange") // Insert at specific position
mutableFruits.addAll(listOf("grape", "kiwi"))
println("After adding: $mutableFruits")
// Remove elements
mutableFruits.remove("banana")
mutableFruits.removeAt(0) // Remove at specific position
mutableFruits.removeAll(listOf("grape", "kiwi"))
println("After removing: $mutableFruits")
// Modify elements
mutableFruits[0] = "pineapple"
println("After modification: $mutableFruits")
// List operations
mutableFruits.sort()
println("After sorting: $mutableFruits")
mutableFruits.reverse()
println("After reversing: $mutableFruits")
mutableFruits.shuffle()
println("After shuffling: $mutableFruits")
// Batch operations
val numbers = mutableListOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
numbers.removeIf { it % 2 == 0 } // Remove even numbers
println("After removing evens: $numbers")
numbers.replaceAll { it * 2 } // Double all elements
println("After doubling: $numbers")
}Functional Operations on Lists
kotlin
data class Person(val name: String, val age: Int, val city: String)
fun main() {
val people = listOf(
Person("Alice", 25, "New York"),
Person("Bob", 30, "London"),
Person("Charlie", 35, "Tokyo"),
Person("Diana", 28, "New York"),
Person("Eve", 32, "London")
)
println("Functional list operations:")
// map - transformation
val names = people.map { it.name }
val ages = people.map { it.age }
val nameAgeMap = people.map { "${it.name} (${it.age})" }
println("Names: $names")
println("Ages: $ages")
println("Name-Age: $nameAgeMap")
// filter - filtering
val adults = people.filter { it.age >= 30 }
val newYorkers = people.filter { it.city == "New York" }
println("Age 30+: ${adults.map { it.name }}")
println("New Yorkers: ${newYorkers.map { it.name }}")
// groupBy - grouping
val peopleByCity = people.groupBy { it.city }
val peopleByAgeGroup = people.groupBy {
when {
it.age < 30 -> "Young"
it.age < 35 -> "Middle"
else -> "Mature"
}
}
println("Grouped by city:")
peopleByCity.forEach { (city, cityPeople) ->
println(" $city: ${cityPeople.map { it.name }}")
}
println("Grouped by age group:")
peopleByAgeGroup.forEach { (ageGroup, agePeople) ->
println(" $ageGroup: ${agePeople.map { it.name }}")
}
// Aggregation operations
val totalAge = people.sumOf { it.age }
val averageAge = people.map { it.age }.average()
val oldestPerson = people.maxByOrNull { it.age }
val youngestPerson = people.minByOrNull { it.age }
println("Total age: $totalAge")
println("Average age: ${"%.1f".format(averageAge)}")
println("Oldest: ${oldestPerson?.name}")
println("Youngest: ${youngestPerson?.name}")
// Chain operations
val result = people
.filter { it.age >= 28 }
.sortedBy { it.age }
.groupBy { it.city }
.mapValues { (_, cityPeople) -> cityPeople.map { it.name } }
println("Chain operation result: $result")
}Sets
Immutable Sets
kotlin
fun main() {
// Create immutable sets
val numbers = setOf(1, 2, 3, 4, 5, 3, 2, 1) // Duplicates are removed
val fruits = setOf("apple", "banana", "cherry")
val emptySet = emptySet<String>()
println("Set creation:")
println("Number set: $numbers") // [1, 2, 3, 4, 5]
println("Fruit set: $fruits")
println("Set size: ${numbers.size}")
// Set checking
println("\nSet checking:")
println("Contains 3: ${3 in numbers}")
println("Contains 6: ${6 in numbers}")
println("Is empty: ${numbers.isEmpty()}")
// Set operations
val set1 = setOf(1, 2, 3, 4, 5)
val set2 = setOf(4, 5, 6, 7, 8)
println("\nSet operations:")
println("set1: $set1")
println("set2: $set2")
println("Union: ${set1 union set2}")
println("Intersection: ${set1 intersect set2}")
println("Difference: ${set1 - set2}")
println("Symmetric difference: ${(set1 - set2) + (set2 - set1)}")
// Subset check
val subset = setOf(2, 3)
println("$subset is subset of $set1: ${subset.all { it in set1 }}")
// Set transformations
val doubled = numbers.map { it * 2 }.toSet()
val filtered = numbers.filter { it > 2 }.toSet()
println("\nSet transformations:")
println("Doubled: $doubled")
println("Filtered > 2: $filtered")
}Mutable Sets
kotlin
fun main() {
// Create mutable sets
val mutableNumbers = mutableSetOf(1, 2, 3)
val hashSet = hashSetOf<String>()
val linkedHashSet = linkedSetOf<String>() // Maintains insertion order
println("Mutable set operations:")
println("Initial set: $mutableNumbers")
// Add elements
mutableNumbers.add(4)
mutableNumbers.add(2) // Duplicate won't be added
mutableNumbers.addAll(setOf(5, 6, 7))
println("After adding: $mutableNumbers")
// Remove elements
mutableNumbers.remove(3)
mutableNumbers.removeAll(setOf(6, 7))
println("After removing: $mutableNumbers")
// Retain operation
mutableNumbers.retainAll(setOf(1, 2, 4, 8)) // Keep only specified elements
println("After retaining: $mutableNumbers")
// LinkedHashSet example (maintains insertion order)
linkedHashSet.addAll(listOf("third", "first", "second"))
println("LinkedHashSet: $linkedHashSet") // Maintains insertion order
// HashSet example (no guaranteed order)
hashSet.addAll(listOf("third", "first", "second"))
println("HashSet: $hashSet") // Order may differ
}Maps
Immutable Maps
kotlin
fun main() {
// Create immutable maps
val capitals = mapOf(
"USA" to "Washington",
"UK" to "London",
"Japan" to "Tokyo",
"France" to "Paris"
)
val numbers = mapOf(1 to "one", 2 to "two", 3 to "three")
val emptyMap = emptyMap<String, String>()
println("Map creation:")
println("Capitals: $capitals")
println("Numbers: $numbers")
// Map access
println("\nMap access:")
println("Capital of USA: ${capitals["USA"]}")
println("Capital of China: ${capitals["China"]}") // null
println("Capital of UK (getValue): ${capitals.getValue("UK")}")
println("Capital of Germany (getOrDefault): ${capitals.getOrDefault("Germany", "Unknown")}")
// Map properties
println("\nMap properties:")
println("Size: ${capitals.size}")
println("Is empty: ${capitals.isEmpty()}")
println("Contains key 'Japan': ${"Japan" in capitals}")
println("Contains value 'Berlin': ${"Berlin" in capitals.values}")
// Keys and values
println("\nKeys and values:")
println("All keys: ${capitals.keys}")
println("All values: ${capitals.values}")
println("All entries: ${capitals.entries}")
// Map traversal
println("\nMap traversal:")
for ((country, capital) in capitals) {
println("Capital of $country is $capital")
}
capitals.forEach { (country, capital) ->
println("$country -> $capital")
}
}Mutable Maps
kotlin
fun main() {
// Create mutable maps
val mutableScores = mutableMapOf(
"Alice" to 95,
"Bob" to 87,
"Charlie" to 92
)
val hashMap = hashMapOf<String, Int>()
val linkedHashMap = linkedMapOf<String, Int>() // Maintains insertion order
println("Mutable map operations:")
println("Initial scores: $mutableScores")
// Add and modify
mutableScores["Diana"] = 89 // Add new entry
mutableScores["Alice"] = 98 // Modify existing entry
mutableScores.put("Eve", 91)
mutableScores.putAll(mapOf("Frank" to 85, "Grace" to 94))
println("After adding: $mutableScores")
// Remove
mutableScores.remove("Bob")
val removedScore = mutableScores.remove("Charlie")
println("Removed Charlie's score: $removedScore")
println("After removing: $mutableScores")
// Conditional operations
mutableScores.putIfAbsent("Alice", 100) // Add only if not exists
mutableScores.replace("Diana", 89, 90) // Conditional replacement
println("After conditional operations: $mutableScores")
// Compute operations
mutableScores.compute("Alice") { _, oldValue -> (oldValue ?: 0) + 2 }
mutableScores.computeIfAbsent("Henry") { 88 }
mutableScores.computeIfPresent("Eve") { _, oldValue -> oldValue + 5 }
println("After compute operations: $mutableScores")
// Merge operation
mutableScores.merge("Grace", 10) { oldValue, newValue -> oldValue + newValue }
println("After merge operation: $mutableScores")
}Advanced Map Operations
kotlin
data class Student(val name: String, val grade: Int, val subject: String)
fun main() {
val students = listOf(
Student("Alice", 95, "Math"),
Student("Bob", 87, "Math"),
Student("Charlie", 92, "Science"),
Student("Diana", 89, "Math"),
Student("Eve", 91, "Science"),
Student("Frank", 85, "English"),
Student("Grace", 94, "English")
)
println("Advanced map operations:")
// Group by subject
val studentsBySubject = students.groupBy { it.subject }
println("Grouped by subject:")
studentsBySubject.forEach { (subject, subjectStudents) ->
println(" $subject: ${subjectStudents.map { it.name }}")
}
// Create name to grade mapping
val nameToGrade = students.associate { it.name to it.grade }
println("\nName to grade mapping: $nameToGrade")
// Calculate average by subject
val averageBySubject = students
.groupBy { it.subject }
.mapValues { (_, subjectStudents) ->
subjectStudents.map { it.grade }.average()
}
println("Average by subject:")
averageBySubject.forEach { (subject, average) ->
println(" $subject: ${"%.1f".format(average)}")
}
// Map transformation
val gradeDistribution = students
.groupingBy {
when {
it.grade >= 90 -> "A"
it.grade >= 80 -> "B"
it.grade >= 70 -> "C"
else -> "D"
}
}
.eachCount()
println("Grade distribution: $gradeDistribution")
// Map filtering
val highPerformers = nameToGrade.filter { (_, grade) -> grade >= 90 }
val mathStudents = studentsBySubject["Math"]?.associate { it.name to it.grade } ?: emptyMap()
println("High performers: $highPerformers")
println("Math students: $mathStudents")
// Map merging
val bonusPoints = mapOf("Alice" to 5, "Bob" to 3, "Charlie" to 2)
val finalGrades = nameToGrade.toMutableMap()
bonusPoints.forEach { (name, bonus) ->
finalGrades.merge(name, bonus) { oldGrade, bonusPoint -> oldGrade + bonusPoint }
}
println("Final grades with bonus: $finalGrades")
}Sequences
Lazy Evaluation
kotlin
fun main() {
// Sequence vs Collection performance comparison
val largeList = (1..1_000_000).toList()
println("Sequence operations:")
// Using collection (eager evaluation)
val listResult = largeList
.filter { it % 2 == 0 }
.map { it * it }
.take(10)
println("Collection result: $listResult")
// Using sequence (lazy evaluation)
val sequenceResult = largeList.asSequence()
.filter { it % 2 == 0 }
.map { it * it }
.take(10)
.toList()
println("Sequence result: $sequenceResult")
// Creating sequences
val sequence1 = sequenceOf(1, 2, 3, 4, 5)
val sequence2 = generateSequence(1) { it + 1 } // Infinite sequence
val sequence3 = generateSequence { kotlin.random.Random.nextInt(100) }
println("Sequence 1 first 5: ${sequence1.take(5).toList()}")
println("Sequence 2 first 10: ${sequence2.take(10).toList()}")
println("Sequence 3 first 5: ${sequence3.take(5).toList()}")
// Fibonacci sequence
val fibonacci = generateSequence(Pair(0, 1)) { (a, b) -> Pair(b, a + b) }
.map { it.first }
println("Fibonacci first 15: ${fibonacci.take(15).toList()}")
// File processing example (simulated)
fun processLargeDataset(): Sequence<String> = sequence {
repeat(1000) { index ->
yield("Data row $index")
// Simulate reading from file or database
}
}
val processedData = processLargeDataset()
.filter { it.contains("5") }
.map { it.uppercase() }
.take(5)
.toList()
println("Processed data: $processedData")
}Practical Applications
Data Analysis Example
kotlin
data class SalesRecord(
val date: String,
val product: String,
val category: String,
val amount: Double,
val quantity: Int,
val region: String
)
class SalesAnalyzer {
fun analyzeSales(records: List<SalesRecord>): Map<String, Any> {
val totalSales = records.sumOf { it.amount }
val totalQuantity = records.sumOf { it.quantity }
val averageOrderValue = totalSales / records.size
// Analyze by product
val salesByProduct = records
.groupBy { it.product }
.mapValues { (_, productRecords) ->
productRecords.sumOf { it.amount }
}
.toList()
.sortedByDescending { it.second }
// Analyze by category
val salesByCategory = records
.groupBy { it.category }
.mapValues { (_, categoryRecords) ->
mapOf(
"totalSales" to categoryRecords.sumOf { it.amount },
"totalQuantity" to categoryRecords.sumOf { it.quantity },
"averagePrice" to categoryRecords.map { it.amount / it.quantity }.average()
)
}
// Analyze by region
val salesByRegion = records
.groupBy { it.region }
.mapValues { (_, regionRecords) -> regionRecords.sumOf { it.amount } }
// Trend analysis
val salesByDate = records
.groupBy { it.date }
.mapValues { (_, dateRecords) -> dateRecords.sumOf { it.amount } }
.toSortedMap()
return mapOf(
"totalSales" to totalSales,
"totalQuantity" to totalQuantity,
"averageOrderValue" to averageOrderValue,
"topProducts" to salesByProduct.take(5),
"categoryAnalysis" to salesByCategory,
"regionAnalysis" to salesByRegion,
"dailyTrend" to salesByDate
)
}
}
fun main() {
val salesRecords = listOf(
SalesRecord("2023-01-01", "Laptop", "Electronics", 999.99, 1, "Beijing"),
SalesRecord("2023-01-01", "Mouse", "Electronics", 29.99, 2, "Shanghai"),
SalesRecord("2023-01-02", "Keyboard", "Electronics", 79.99, 1, "Guangzhou"),
SalesRecord("2023-01-02", "Monitor", "Electronics", 299.99, 1, "Shenzhen"),
SalesRecord("2023-01-03", "Coffee", "Food", 15.99, 3, "Beijing"),
SalesRecord("2023-01-03", "Tea", "Food", 25.99, 2, "Shanghai"),
SalesRecord("2023-01-04", "Laptop", "Electronics", 1199.99, 1, "Guangzhou"),
SalesRecord("2023-01-04", "Phone", "Electronics", 699.99, 1, "Shenzhen")
)
val analyzer = SalesAnalyzer()
val analysis = analyzer.analyzeSales(salesRecords)
println("Sales Data Analysis Report:")
println("Total sales: ${"%.2f".format(analysis["totalSales"])}")
println("Total quantity: ${analysis["totalQuantity"]}")
println("Average order value: ${"%.2f".format(analysis["averageOrderValue"])}")
println("\nTop 5 Products:")
@Suppress("UNCHECKED_CAST")
val topProducts = analysis["topProducts"] as List<Pair<String, Double>>
topProducts.forEach { (product, sales) ->
println(" $product: ${"%.2f".format(sales)}")
}
println("\nCategory Analysis:")
@Suppress("UNCHECKED_CAST")
val categoryAnalysis = analysis["categoryAnalysis"] as Map<String, Map<String, Any>>
categoryAnalysis.forEach { (category, stats) ->
println(" $category:")
println(" Total sales: ${"%.2f".format(stats["totalSales"])}")
println(" Total quantity: ${stats["totalQuantity"]}")
println(" Average price: ${"%.2f".format(stats["averagePrice"])}")
}
}Performance Optimization
Collection Selection Guide
kotlin
fun main() {
println("Collection performance guide:")
// 1. Choose appropriate collection type based on use case
// Need to maintain insertion order and allow duplicates -> List
val orderHistory = mutableListOf<String>()
// Need uniqueness and don't care about order -> HashSet
val uniqueVisitors = hashSetOf<String>()
// Need uniqueness and maintain insertion order -> LinkedHashSet
val orderedCategories = linkedSetOf<String>()
// Need key-value mapping and don't care about order -> HashMap
val userCache = hashMapOf<String, Any>()
// Need key-value mapping and maintain insertion order -> LinkedHashMap
val orderedConfig = linkedMapOf<String, String>()
// 2. Use sequences for large data processing
fun processLargeDataEfficiently(data: List<Int>): List<Int> {
return data.asSequence()
.filter { it > 100 }
.map { it * 2 }
.take(1000)
.toList()
}
// 3. Avoid unnecessary intermediate collections
val numbers = (1..1000).toList()
// Bad: creates multiple intermediate collections
val result1 = numbers
.filter { it % 2 == 0 }
.map { it * it }
.filter { it > 100 }
.sorted()
// Good: use sequence to reduce intermediate collections
val result2 = numbers.asSequence()
.filter { it % 2 == 0 }
.map { it * it }
.filter { it > 100 }
.sorted()
.toList()
println("Optimized result size: ${result2.size}")
}Best Practices
1. Choose Appropriate Collection Types
kotlin
// Choose collection type based on requirements
class BestPractices {
// Use immutable collections for read-only data
val supportedLanguages = listOf("Kotlin", "Java", "Python", "JavaScript")
// Use mutable collections for data that needs modification
private val _activeUsers = mutableSetOf<String>()
val activeUsers: Set<String> = _activeUsers // Expose read-only view
// Use sequences for large datasets
fun processLargeDataset(data: List<String>): List<String> {
return data.asSequence()
.filter { it.isNotBlank() }
.map { it.trim().lowercase() }
.distinct()
.sorted()
.toList()
}
// Use appropriate collection operations
fun findUsersByCity(users: List<Person>, city: String): List<Person> {
return users.filter { it.city == city } // Instead of manual loop
}
}2. Null Safety and Error Handling
kotlin
fun safeCollectionOperations() {
val numbers: List<Int>? = null
val emptyList = emptyList<Int>()
val normalList = listOf(1, 2, 3, 4, 5)
// Safe access
println("First element of null list: ${numbers?.firstOrNull()}")
println("Size of null list: ${numbers?.size ?: 0}")
// Use safe operations
val result = normalList
.takeIf { it.isNotEmpty() }
?.filter { it > 2 }
?.map { it * 2 }
?: emptyList()
println("Safe operation result: $result")
}Next Steps
After mastering arrays and collections, let's learn about interfaces and abstract classes in Kotlin.
Next Chapter: Interfaces and Abstract Classes
Exercises
- Implement a student grade management system using different collection types to store data
- Create a library management system that supports CRUD operations for books
- Write a data analysis tool that processes large sales data and generates reports
- Implement a caching system using maps to store key-value pairs with expiration time support
- Design a recommendation system based on user behavior data