Kotlin's Collections in a Nutshell
[Background Image by JOHN TOWNER]
Kotlin, known for its conciseness, expressiveness, and interoperability with Java, provides powerful tools for working with collections.
What is Collections in Kotlin?
The Collections class in Kotlin is like a toolbox full of useful tools for working with groups of items, like lists or sets. Just like a toolbox helps us organize and manage our tools, the Collections class helps us to organize and manage our data.
Imagine we have a bunch of books scattered around our room. The Collections class helps us neatly arrange them on our bookshelf, sort them by title or author, find a specific book quickly, or even remove some books we don't need anymore.
In programming terms, the Collections class provides methods for tasks like sorting a list, finding the minimum or maximum value, shuffling elements randomly, searching for specific items, and much more. It's like having a set of handy functions to make working with collections of data easier and more efficient in our Kotlin code.
Overview of Kotlin Collections
Before diving deep into the Collections
class, let's briefly recap Kotlin's collections hierarchy:
Immutable Collections: These collections, once created, cannot be modified. They provide a read-only view of the data. Examples include
List
,Set
, andMap
.Mutable Collections: These collections can be modified after creation. Examples include
MutableList
,MutableSet
, andMutableMap
.
Now, let's focus on the Collections
class.
Functionality
The Collections
class serves as a utility class for common operations on collections. It provides methods for creating instances of various collection types, as well as utility functions for working with collections.
1. Factory Methods
emptyList()
,emptySet()
,emptyMap()
: These methods return empty instances ofList
,Set
, andMap
, respectively.listOf()
,setOf()
,mapOf()
: These methods create immutable instances ofList
,Set
, andMap
containing the specified elements.mutableListOf()
,mutableSetOf()
,mutableMapOf()
: These methods create mutable instances ofList
,Set
, andMap
containing the specified elements.
2. Utility Functions
reverse()
: Reverses the order of elements in a list.shuffle()
: Shuffles the elements in a list randomly.sort()
: Sorts the elements in a list in natural order or using a custom comparator.binarySearch()
: Searches for an element in a sorted list using binary search.
3. Type Conversion
toMutableList()
,toMutableSet()
,toMutableMap()
: Converts an immutable collection to a mutable one.
Common Examples
Sorting a List:
val list = mutableListOf(3, 1, 4, 1, 5, 9, 2, 6) val sortedList = Collections.sort(list) println(sortedList) // Output: [1, 1, 2, 3, 4, 5, 6, 9]
Finding Minimum and Maximum:
val list = listOf(3, 1, 4, 1, 5, 9, 2, 6) val min = Collections.min(list) val max = Collections.max(list) println("Min: $min, Max: $max") // Output: Min: 1, Max: 9
Reversing a List:
val list = mutableListOf(1, 2, 3, 4, 5) Collections.reverse(list) println(list) // Output: [5, 4, 3, 2, 1]
Shuffling a List:
val list = mutableListOf(1, 2, 3, 4, 5) Collections.shuffle(list) println(list) // Output: [3, 2, 1, 5, 4] (Randomly shuffled)
Binary Search:
val list = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9) val index = Collections.binarySearch(list, 5) println("Index of 5: $index") // Output: Index of 5: 4
Creating Immutable Collections:
val immutableList = Collections.singletonList("Hello") println(immutableList) // Output: [Hello]
Finding Frequency of Elements:
val list = listOf(1, 2, 3, 1, 2, 1, 3, 4, 5) val frequencyMap = HashMap<Int, Int>() for (item in list) { frequencyMap[item] = Collections.frequency(list, item) } println(frequencyMap) // Output: {1=3, 2=2, 3=2, 4=1, 5=1}
Swapping Elements:
val list = mutableListOf(1, 2, 3, 4, 5) Collections.swap(list, 0, 4) println(list) // Output: [5, 2, 3, 4, 1]
Checking for Equality of Two Lists:
val list1 = listOf(1, 2, 3, 4, 5) val list2 = listOf(5, 4, 3, 2, 1) val isEqual = Collections.disjoint(list1, list2) println("Are lists equal: ${!isEqual}") // Output: Are lists equal: true
Filling a List with Specified Value:
val list = mutableListOf<Int>() Collections.fill(list, 10) println(list) // Output: [10, 10, 10, 10, 10] (List filled with 10)
Creating Synchronized Collections:
val synchronizedList = Collections.synchronizedList(mutableListOf(1, 2, 3))
Finding Sublist:
val list = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9) val subList = list.subList(2, 5) println(subList) // Output: [3, 4, 5]
These are just a few examples of the functionality provided by the Collections
class in Kotlin. It offers a wide range of utility methods for working with collections efficiently.
Remember, Kotlin also provides extension functions and other methods on collection types directly, so we might not always need to use Collections
class directly.
Practical Usages
E-commerce Platform: In an e-commerce platform, we may need to work with collections of products, orders, and customers. We can use Kotlin's
Collections
class for tasks like sorting products by price, finding the most popular products based on order frequency, or creating a list of recommended products for a specific customer based on their purchase history.Social Media Analytics Tool: When building a social media analytics tool, we might collect data such as likes, comments, and shares for posts. We can use Kotlin's
Collections
class to analyze this data, such as finding the most engaging posts, identifying trends based on hashtags, or clustering users based on their interactions with content.Inventory Management System: In an inventory management system for a warehouse or retail store, we'll work with collections of items, orders, and shipments. Kotlin's
Collections
class can help with tasks like sorting inventory by category or quantity, finding items that need restocking, or optimizing routes for order fulfillment based on the location of items in the warehouse.Travel Booking Platform: When developing a travel booking platform, we'll deal with collections of flights, hotels, and itineraries. Kotlin's
Collections
class can be useful for tasks like filtering available flights based on departure time or price, sorting hotels by rating or proximity to attractions, or generating personalized travel packages for users based on their preferences.Healthcare Management System: In a healthcare management system, we'll handle collections of patient records, appointments, and medical procedures. Kotlin's
Collections
class can assist with tasks like organizing patient data by demographics or medical history, scheduling appointments based on availability of doctors and facilities, or analyzing trends in patient diagnoses and treatments.
These examples illustrate how Kotlin's Collections
class can be applied in various real-life projects to efficiently manage and process data.
Managing Threads Effectively
Kotlin's coroutines are a powerful feature for asynchronous programming and managing concurrency. When combined with collections functionality, they provide an efficient way to process data concurrently while managing threads effectively. Here's how we can use coroutines with collections for thread management:
Parallel Processing with
map
:import kotlinx.coroutines.* suspend fun processData(item: Int): String { // Simulate some processing delay delay(1000) return "Processed: $item" } suspend fun main() { val list = listOf(1, 2, 3, 4, 5) val processedData = list.map { item -> // Launch a coroutine to process each item concurrently async { processData(item) } }.awaitAll() // Wait for all coroutines to complete println(processedData) }
Concurrency with
forEach
:import kotlinx.coroutines.* suspend fun processData(item: Int): String { // Simulate some processing delay delay(1000) return "Processed: $item" } suspend fun main() { val list = listOf(1, 2, 3, 4, 5) val deferredList = mutableListOf<Deferred<String>>() list.forEach { item -> // Launch a coroutine to process each item concurrently val deferred = withContext(Dispatchers.Default) { processData(item) } deferredList.add(deferred) } val processedData = deferredList.awaitAll() // Wait for all coroutines to complete println(processedData) }
Batch Processing with
chunked
:import kotlinx.coroutines.* suspend fun processDataBatch(batch: List<Int>): List<String> { // Simulate some processing delay delay(1000) return batch.map { item -> "Processed: $item" } } suspend fun main() { val list = (1..10).toList() val batchSize = 3 val processedData = list.chunked(batchSize).map { batch -> // Launch a coroutine to process each batch concurrently withContext(Dispatchers.Default) { processDataBatch(batch) } }.flatMap { it.await() } // Wait for all coroutines to complete and flatten the results println(processedData) }
Filtering Concurrently:
import kotlinx.coroutines.* suspend fun checkCondition(item: Int): Boolean { // Simulate some condition checking delay delay(1000) return item % 2 == 0 } suspend fun main() { val list = (1..10).toList() val filteredData = list.map { item -> // Launch a coroutine to check condition concurrently async { item to checkCondition(item) } }.awaitAll().filter { it.second }.map { it.first } // Wait for all coroutines and filter results println(filteredData) }
Concurrent Data Transformation:
import kotlinx.coroutines.* suspend fun transformData(item: Int): String { // Simulate some transformation delay delay(1000) return "Transformed: $item" } suspend fun main() { val list = (1..10).toList() val transformedData = list.map { item -> // Launch a coroutine to transform data concurrently withContext(Dispatchers.Default) { transformData(item) } }.map { it.await() } // Wait for all coroutines and collect results println(transformedData) }
Concurrent Data Processing with
zip
:import kotlinx.coroutines.* suspend fun processData(item1: Int, item2: Int): Int { // Simulate some processing delay delay(1000) return item1 + item2 } suspend fun main() { val list1 = listOf(1, 2, 3, 4, 5) val list2 = listOf(6, 7, 8, 9, 10) val processedData = list1.zip(list2).map { (item1, item2) -> // Launch a coroutine to process data concurrently async { processData(item1, item2) } }.map { it.await() } // Wait for all coroutines and collect results println(processedData) }
These examples showcase how Kotlin coroutines can be used with collections to perform process collections concurrently, thereby optimizing performance various concurrent tasks such as filtering, transformation, and processing, and efficiently utilizing threads and improving performance.
Extensions Functions
The Kotlin standard library provides a variety of extension functions for working with collections. Here are some of them along with examples:
filter(): Returns a list containing only elements matching the given predicate.
val list = listOf(1, 2, 3, 4, 5) val filteredList = list.filter { it % 2 == 0 } println(filteredList) // Output: [2, 4]
map(): Returns a list containing the results of applying the given transform function to each element in the original collection.
val list = listOf(1, 2, 3, 4, 5) val squaredList = list.map { it * it } println(squaredList) // Output: [1, 4, 9, 16, 25]
flatMap(): Returns a single list of all elements resulting from applying the given transform function to each element of the original collection.
val nestedList = listOf(listOf(1, 2), listOf(3, 4), listOf(5, 6)) val flattenedList = nestedList.flatMap { it } println(flattenedList) // Output: [1, 2, 3, 4, 5, 6]
distinct(): Returns a list containing only distinct elements from the original collection.
val list = listOf(1, 2, 2, 3, 3, 3, 4, 4, 4, 4) val distinctList = list.distinct() println(distinctList) // Output: [1, 2, 3, 4]
groupBy(): Groups elements of the original collection by the key returned by the given keySelector function.
data class Person(val name: String, val age: Int) val people = listOf( Person("Alice", 20), Person("Bob", 30), Person("Alice", 25) ) val groupedByAge = people.groupBy { it.age } println(groupedByAge) // Output: {20=[Person(name=Alice, age=20)], 30=[Person(name=Bob, age=30), Person(name=Alice, age=25)]}
sortedBy(): Returns a list of all elements sorted according to natural sort order of the value returned by specified selector function.
val people = listOf( Person("Alice", 20), Person("Bob", 30), Person("Charlie", 25) ) val sortedByName = people.sortedBy { it.name } println(sortedByName) // Output: [Person(name=Alice, age=20), Person(name=Bob, age=30), Person(name=Charlie, age=25)]
associateBy(): Returns a map containing key-value pairs provided by the given transform function applied to each element of the original collection.
val people = listOf( Person("Alice", 20), Person("Bob", 30), Person("Charlie", 25) ) val mapByName = people.associateBy { it.name } println(mapByName) // Output: {Alice=Person(name=Alice, age=20), Bob=Person(name=Bob, age=30), Charlie=Person(name=Charlie, age=25)}
partition(): Splits the original collection into pair of lists, where first list contains elements for which the predicate yielded true, while the second list contains elements for which the predicate yielded false.
val list = listOf(1, 2, 3, 4, 5) val (even, odd) = list.partition { it % 2 == 0 } println("Even: $even, Odd: $odd") // Output: Even: [2, 4], Odd: [1, 3, 5]
These are just a few examples of extension functions provided by the Kotlin standard library for working with collections. They make working with collections in Kotlin more concise and expressive.
That's it for today. Happy Coding...
Subscribe to my newsletter
Read articles from Romman Sabbir directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Romman Sabbir
Romman Sabbir
Senior Android Engineer from Bangladesh. Love to contribute in Open-Source. Indie Music Producer.