Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Introduction to Kotlin Collections and Their Construction

Tech 2

Kotlin Collections Overview

Kotlin's kotlin.collections package provides three primary collection types: List (ordered), Set (unordered, unique elements), and Map (key-value pairs).

A collection defined with val is immutable in its reference, not necessarily its content. You can modify a mutable collection, but you cannot reassign the variable to a new collection instance.

val items = mutableListOf("apple", "banana", "cherry")
items.add("date") // This is allowed.
// items = mutableListOf("elderberry", "fig") // Compilation error: Val cannot be reassigned.

The Collection Interface

Collection<T> is the root of the collection hierarchy, representing read-only behaviors like checking size and membership. It inherits from Iterable<T>. For more specific use cases, use its inheritors: List and Set.

fun displayItems(elements: Collection<String>) {
    for (element in elements) print("$element ")
    println()
}

fun main() {
    val fruitList = listOf("apple", "banana", "apple")
    displayItems(fruitList)
    
    val fruitSet = setOf("apple", "banana", "cherry")
    displayItems(fruitSet)
}

MutableCollection extends Collection with write operations like add and remove.

fun List<String>.extractShortWords(result: MutableList<String>, maxLen: Int) {
    this.filterTo(result) { it.length <= maxLen }
    val determiners = setOf("a", "A", "an", "An", "the", "The")
    result -= determiners
}

fun main() {
    val sentence = "A long time ago in a galaxy far far away".split(" ")
    val shortWords = mutableListOf<String>()
    sentence.extractShortWords(shortWords, 3)
    println(shortWords) // Output: [ago, in, far, far]
}

List

List<T> stores elements in a defined sequence and allows indexed access starting from 0.

val colors = listOf("red", "green", "blue", "yellow")
println("Element count: ${colors.size}")
println("Third element: ${colors.get(2)}")
println("Fourth element: ${colors[3]}")
println("Index of 'green': ${colors.indexOf("green")}")

Lists can contain duplicate elements, including null. Two lists are equal if they have the same size and identical elements in the same order.

data class User(val name: String, var age: Int)

val alice = User("Alice", 30)
val group1 = listOf<User>(User("Bob", 25), alice, alice)
val group2 = listOf<User>(User("Bob", 25), User("Alice", 30), alice)
println(group1 == group2) // true

alice.age = 31
println(group1 == group2) // false

MutableList supports write operations.

val values = mutableListOf(10, 20, 30, 40)
values.add(50)
values.removeAt(1)
values[0] = 5
values.shuffle()
println(values)

The default List implementation in Kotlin is ArrayList.

Set

Set<T> stores unique elements; order is generally undefined. A Set can contain at most one null. Two sets are equal if they have the same size and for every element in one set, there is an equal element in the other.

val digits = setOf(5, 6, 7, 8)
println("Set size: ${digits.size}")
if (digits.contains(5)) println("5 is present")

val reversedDigits = setOf(8, 7, 6, 5)
println("Sets are equal: ${digits == reversedDigits}") // true

MutableSet adds write operations. The default implementation, LinkedHashSet, preserves insertion order.

val firstSet = setOf(1, 2, 3, 4) // LinkedHashSet
val secondSet = setOf(4, 3, 2, 1)
println(firstSet.first() == secondSet.first()) // false
println(firstSet.first() == secondSet.last()) // true

HashSet does not guarantee order but uses less memory.

Map

Map<K, V> stores key-value pairs (entries). Keys are unique, but different keys can map to the same value.

val dataMap = mapOf("alpha" to 10, "beta" to 20, "gamma" to 30, "delta" to 10)
println("Keys: ${dataMap.keys}")
println("Values: ${dataMap.values}")
if ("beta" in dataMap) println("Value for 'beta': ${dataMap["beta"]}")
if (10 in dataMap.values) println("The value 10 exists in the map")

MutableMap supports adding or updating entries.

val mutableData = mutableMapOf("first" to 1, "second" to 2)
mutableData.put("third", 3)
mutableData["first"] = 100
println(mutableData)

The default Map implementation is LinkedHashMap. HashMap does not guarentee order.

Constructing Collections

From Elements

The most common way is using library functions: listOf<T>(), setOf<T>(), mutableListOf<T>(), mutableSetOf<T>(). For empty collections, specify the type explicit.

val animalSet = setOf("cat", "dog", "bird")
val emptyMutableSet = mutableSetOf<String>()

For maps, use mapOf() and mutableMapOf(). Pairs are often created with the to infix function.

val countryCodes = mapOf("US" to 1, "UK" to 44, "DE" to 49)

Note: to creates a short-lived Pair. For better performance with mutable maps, consider alternative initialization.

val betterMap = mutableMapOf<String, Int>().apply {
    this["one"] = 1
    this["two"] = 2
}

Empty Collections

Use emptyList(), emptySet(), emptyMap(). Specify the element type.

val nothingHere = emptyList<Double>()

List Initializer Function

List has a constructor that takes a size and an initializer function.

val squares = List(5) { index -> index * index }
println(squares) // [0, 1, 4, 9, 16]

Concrete Type Constructors

You can use constructors of specific implementations like ArrayList or HashSet.

val linkedList = LinkedList<String>(listOf("x", "y", "z"))
val sizedHashSet = HashSet<Int>(64)

Copying Collections

Copy operations like toList(), toMutableList(), toSet() create shallow copies. Changes to the original collection's elements affect all copies, but adding/removing elements does not.

val original = mutableListOf(100, 200, 300)
val mutableCopy = original.toMutableList()
val readOnlyCopy = original.toList()

original.add(400)
println("Mutable copy size: ${mutableCopy.size}") // 3
// readOnlyCopy.add(400) // Compilation error
println("Read-only copy size: ${readOnlyCopy.size}") // 3

These functoins can also convert between collection types.

val numberList = mutableListOf(7, 8, 9)
val numberSet = numberList.toMutableSet()
numberSet.add(9) // No effect, 9 already exists
numberSet.add(10)
println(numberSet) // [7, 8, 9, 10]

Assigning an existing collection to a new variable creates a reference, not a copy.

val source = mutableListOf("a", "b")
val reference = source
reference.add("c")
println("Source size: ${source.size}") // 3

Initialization can be used to restrict mutability.

val mutableSource = mutableListOf(1.0, 2.0)
val readOnlyView: List<Double> = mutableSource
// readOnlyView.add(3.0) // Compilation error
mutableSource.add(3.0)
println(readOnlyView) // [1.0, 2.0, 3.0]

Invoking Functions on Other Collections

New collections can be created as results of operations like filtering or mapping.

val words = listOf("hello", "world", "kotlin", "code")
val longWords = words.filter { it.length > 4 }
println(longWords) // [hello, world, kotlin]
val nums = setOf(1, 2, 3)
println(nums.map { it * 10 }) // [10, 20, 30]
println(nums.mapIndexed { i, v -> v * i }) // [0, 2, 6]
val items = listOf("book", "pen", "laptop")
println(items.associateWith { it.length }) // {book=4, pen=3, laptop=6}

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.