Collection and its subtypes implement Iterable — Map does not:
/**
* Classes that inherit from this interface can be represented as a sequence of elements that can be iterated over.
* @param T is the type of element being iterated over. The iterator is covariant in its element type.
*/
public interface Iterable<out T> {
public operator fun iterator(): Iterator<T>
}
This enables the for loop syntax:
for (element in myCollection) {
println(element)
}
Collection subtypes are Iterable; MutableCollection subtypes are also MutableIterable. Map is neither.
val iterator = myIterableCollection.iterator()
while (iterator.hasNext()) {
iterator.next()
}
MutableIterable returns a MutableIterator, which adds remove()
val iterator = myMutableIterableCollection.iterator()
while (iterator.hasNext()) {
iterator.next()
iterator.remove() // removes the element last returned by next()
}
Kotlin separates read-only and mutable views of collections via two interfaces.
Collection<E> provides read-only access — it has no add or remove.
MutableCollection<E> extends both Collection and MutableIterable, adding mutation operations.
Note: read-only does not mean immutable — a Collection variable may point to an underlying MutableList that is mutated elsewhere.
val readonlyCollection = listOf(1, 2, 3)
readonlyCollection.add(4) // ERROR: Unresolved reference: add
val mutableCollection = mutableListOf(1, 2, 3)
mutableCollection.add(4) // OK
If you create a mutable collection, you cannot reassign the val variable
val mutableCollection = mutableListOf(1, 2, 3)
mutableCollection.add(4) // OK
mutableCollection = mutableListOf(4, 5, 6) // ERROR: Val cannot be reassigned
But you can reassign var
var mutableCollection = mutableListOf(1, 2, 3)
mutableCollection.add(4) // OK
mutableCollection = mutableListOf(4, 5, 6) // OK
Each collection has several core interface methods:
public interface Collection<out E> : Iterable<E> {
public val size: Int
public fun isEmpty(): Boolean // Use this instead of size == 0
public operator fun contains(element: @UnsafeVariance E): Boolean
public fun containsAll(elements: Collection<@UnsafeVariance E>): Boolean
...
}
Collection also has useful extension functions:
// Convenient to use in loops:
// for (i in collection.indices) { ... }
public val Collection<*>.indices: IntRange
get() = 0..size - 1
public val <T> List<T>.lastIndex: Int
get() = this.size - 1
// Use this instead of size != 0
public inline fun <T> Collection<T>.isNotEmpty(): Boolean = !isEmpty()
public interface List<out E> : Collection<E> {
public operator fun get(index: Int): E // Convenient to use with []: collection[2]
public fun indexOf(element: @UnsafeVariance E): Int
public fun lastIndexOf(element: @UnsafeVariance E): Int
public fun subList(fromIndex: Int, toIndex: Int): List<E>
...
}
subList returns a live view backed by the original list — mutations are reflected in both:
val list1 = mutableListOf(1, 2, 3)
val list2 = list1.subList(0, 1)
list1[0] += 1
println(list1) // [2, 2, 3]
println(list2) // [2]
To create a new list you can use special builders (by default ArrayList):
val list1 = emptyList<Int>() // Builds the internal object EmptyList
val list2 = listOf<Int>() // Calls emptyList()
val list3 = listOf(1, 2, 3) // The type can be inferred
val list4 = mutableListOf<Int>() // backed by ArrayList<Int>
val list5 = mutableListOf(1, 2, 3) // The type can be inferred
val list6 = buildList {
// constructs MutableList<Int>
add(5)
addAll(0, listOf(1, 2, 3))
}
public interface Set<out E> : Collection<E>
// No new members — all interface methods are inherited from Collection
A generic unordered collection that does not support duplicate elements.
Order of elements is not guaranteed.
Equality is determined by equals() and hashCode(), not reference identity.
class A(val primary: Int, val secondary: Int)
class B(val primary: Int, val secondary: Int) {
override fun hashCode(): Int = primary
override fun equals(other: Any?) = primary == (other as? B)?.primary
}
fun main() {
val a = A(1,1)
val b = A(1,2)
val set = setOf(a, b)
println(set) // two elements
}
fun main() {
val a = B(1,1)
val b = B(1,2)
val set = setOf(a, b)
println(set) // only one element
}
To create a new set you can use special builders (by default LinkedHashSet):
val set1 = emptySet<Int>() // Builds the internal object EmptySet
val set2 = setOf<Int>() // Calls emptySet()
val set3 = setOf(1, 2, 3) // The type can be inferred
val set4 = mutableSetOf<Int>() // backed by LinkedHashSet<Int>()
val set5 = mutableSetOf(1, 2, 3) // The type can be inferred
val set6 = buildSet {
// constructs MutableSet<Int>
add(5)
addAll(listOf(1, 2, 3))
}
public interface Map<K, out V> {
public fun containsKey(key: K): Boolean
public fun containsValue(value: @UnsafeVariance V): Boolean
public operator fun get(key: K): V?
public fun getOrDefault(key: K, defaultValue: @UnsafeVariance V): V
public val entries: Set<Map.Entry<K, V>>
// Convenient to use in loops:
// for ((key, value) in map) { ... }
// Note: map is more idiomatic compared to map.entries
...
}
To create a new map you can use special builders (by default LinkedHashMap):
val map1 = emptyMap<Int, String>() // Builds the internal object EmptyMap
val map2 = mapOf<Int, String>() // Calls emptyMap()
val map3 = mapOf(1 to "one", 2 to "two") // The type can be inferred
val map4 = mutableMapOf<Int, String>() // backed by LinkedHashMap<...>()
val map5 = mutableMapOf(1 to "one", 2 to "two") // The type can be inferred
val map6 = buildMap {
// constructs MutableMap<Int, String>
put(1, "one")
putAll(mapOf(2 to "two"))
}
Does not implement Collection or Iterable, but supports for loops via an extension iterator()
Has a fixed size, but its elements are mutable
/**
* Represents an array (specifically a Java array when targeting the JVM platform).
* Array instances can be created using arrayOf, arrayOfNulls, and emptyArray
*/
public class Array<T> {
public val size: Int
public operator fun get(index: Int): T
public operator fun set(index: Int, value: T): Unit
...
}
Kotlin also has classes that represent arrays of primitive types without boxing overhead: ByteArray, ShortArray, IntArray, and so on.
/**
* An array of ints. When targeting the JVM, instances of this class are represented as `int[]`.
*/
public class IntArray(size: Int) {
public val size: Int
public operator fun get(index: Int): Int
public operator fun set(index: Int, value: Int): Unit
...
}
Not collections, but support iteration — standard types have IntRange, CharRange, LongRange (each extends the corresponding IntProgression, CharProgression, LongProgression):
for (c in 'a'..'c') { ... } // CharRange — 'a', 'b', 'c'
for (i in 1..5) { ... } // IntRange — 1, 2, 3, 4, 5
for (i in 1 until 5) { ... } // IntRange — 1, 2, 3, 4 (exclusive end)
for (i in 1L..5L) { ... } // LongRange — 1L, 2L, 3L, 4L, 5L
downTo and step are infix extension functions for customizing direction and stride:
for (i in 10 downTo 0 step 3) { ... } // 10, 7, 4, 1
Not a collection, but has an iterator:
/**
* A sequence that returns values through its iterator.
* The values are evaluated lazily, and the sequence is potentially infinite.
*/
public interface Sequence<out T> {
public operator fun iterator(): Iterator<T>
}
To create a new sequence you can use special builders
val sequence1 = emptySequence<Int>() // Builds the internal object EmptySequence
val sequence2 = sequenceOf<Int>() // Calls emptySequence()
val sequence3 = sequenceOf(1, 2, 3) // The type can be inferred
val sequence4 = sequence {
// constructs Sequence<Int>
yield(1)
yieldAll(listOf(2, 3))
}
val sequence5 = generateSequence(1) { it + 2 } // `it` is the previous element
println(sequence5.take(5).toList()) // [1, 3, 5, 7, 9]
val words = "The quick brown fox jumps over the lazy dog".split(" ")
List — eager: each step materializes a full intermediate collection before the next step begins
val lengthsList = words
.filter { it.length > 3 } // all 9 words evaluated → 5-element intermediate List
.map { it.length } // all 5 mapped → another intermediate List
.take(4) // [5, 5, 5, 4]
Sequence — lazy: elements flow one at a time; stops as soon as take(4) is satisfied
val lengthsSeq = words.asSequence()
.filter { it.length > 3 }
.map { it.length }
.take(4)
.toList() // only 6 of 9 words examined — "the", "lazy", "dog" never touched
// [5, 5, 5, 4]
There are many different functions for working with collections. If you need to do something with a collection, Google it first. Most likely, the standard library already has the function you need, for example:
public fun <T : Comparable<T>> List<T?>.binarySearch(element: T?, fromIndex: Int = 0, toIndex: Int = size): Int
// list must be sorted first (e.g. sort() above)
public fun <T : Comparable<T>> MutableList<T>.sort(): Unit
public inline fun <T, K, V> Iterable<T>.groupBy(keySelector: (T) -> K, valueTransform: (T) -> V): Map<K, List<V>>
public inline fun <T> Iterable<T>.partition(predicate: (T) -> Boolean): Pair<List<T>, List<T>>
Thanx!
Iterable
Collection
List
Set
AbstractCollection
MutableIterable
MutableCollection
MutableList
MutableSet
AbstractMutableCollection
Map (extended to contain Entry)
MutableMap
ARROWS
Collection -> Iterable
Row 1: MutableIterable, Iterable
MutableIterable → Iterable
Row 2: MutableCollection, Collection
MutableCollection → MutableIterable
Row 3: List, Set, Map
List → Collection
Row 4: MutableList, MutableSet, MutableMap
MutableList → List
MutableList → MutableCollection (long diagonal)