Maps
A
Mapconnects keys to values and looks up a value when given a key.
You create a Map by providing key-value pairs to mapOf(). Using to, we
separate each key from its associated value:
// Maps/Maps.kt
import atomictest.eq
fun main() {
val constants = mapOf(
"Pi" to 3.141,
"e" to 2.718,
"phi" to 1.618
)
constants eq
"{Pi=3.141, e=2.718, phi=1.618}"
// Look up a value from a key:
constants["e"] eq 2.718 // [1]
constants.keys eq setOf("Pi", "e", "phi")
constants.values eq "[3.141, 2.718, 1.618]"
var s = ""
// Iterate through key-value pairs:
for (entry in constants) { // [2]
s += "${entry.key}=${entry.value}, "
}
s eq "Pi=3.141, e=2.718, phi=1.618,"
s = ""
// Unpack during iteration:
for ((key, value) in constants) // [3]
s += "$key=$value, "
s eq "Pi=3.141, e=2.718, phi=1.618,"
}
-
[1] The
[]operator looks up a value using a key. You can produce all the keys usingkeysand all the values usingvalues. Callingkeysproduces aSetbecause all keys in aMapmust be unique, otherwise you’d have ambiguity during a lookup. -
[2] Iterating through a
Mapproduces key-value pairs as map entries. - [3] You can unpack keys and values as you iterate.
A plain Map is read-only. Here’s a MutableMap:
// Maps/MutableMaps.kt
import atomictest.eq
fun main() {
val m =
mutableMapOf(5 to "five", 6 to "six")
m[5] eq "five"
m[5] = "5ive"
m[5] eq "5ive"
m += 4 to "four"
m eq mapOf(5 to "5ive",
4 to "four", 6 to "six")
}
map[key] = value adds or changes the value associated with key.
You can also explicitly add a pair by saying map += key to value.
mapOf() and mutableMapOf() preserve the order in which the elements are
put into the Map. This is not guaranteed for other types of Map.
A read-only Map doesn’t allow mutations:
// Maps/ReadOnlyMaps.kt
import atomictest.eq
fun main() {
val m = mapOf(5 to "five", 6 to "six")
m[5] eq "five"
// m[5] = "5ive" // Fails
// m += (4 to "four") // Fails
m + (4 to "four") // Doesn't change m
m eq mapOf(5 to "five", 6 to "six")
val m2 = m + (4 to "four")
m2 eq mapOf(
5 to "five", 6 to "six", 4 to "four")
}
The definition of m creates a Map associating Ints with Strings. If we
try to replace a String, Kotlin emits an error.
An expression with + creates a new Map that includes both the old elements
and the new one, but doesn’t affect the original Map. The only way to “add”
an element to a read-only Map is by creating a new Map.
A Map returns null if it doesn’t contain an entry for a given key. If you
need a result that can’t be null, use getValue() and catch
NoSuchElementException if the key is missing:
// Maps/GetValue.kt
import atomictest.*
fun main() {
val map = mapOf('a' to "attempt")
map['b'] eq null
capture {
map.getValue('b')
} eq "NoSuchElementException: " +
"Key b is missing in the map."
map.getOrDefault('a', "??") eq "attempt"
map.getOrDefault('b', "??") eq "??"
}
getOrDefault() is usually a nicer alternative to null or an exception.
You can store class instances as values in a Map. Here’s a map that retrieves
a Contact using a number String:
// Maps/ContactMap.kt
package maps
import atomictest.eq
class Contact(
val name: String,
val phone: String
) {
override fun toString() =
"Contact('$name', '$phone')"
}
fun main() {
val miffy = Contact("Miffy", "1-234-567890")
val cleo = Contact("Cleo", "098-765-4321")
val contacts = mapOf(
miffy.phone to miffy,
cleo.phone to cleo)
contacts["1-234-567890"] eq miffy
contacts["1-111-111111"] eq null
}
It’s possible to use class instances as keys in a Map, but that’s trickier so
we discuss it later in the book.
- -
Maps look like simple little databases. They are sometimes called
associative arrays, because they associate keys with values. Although they
are quite limited compared to a full-featured database, they are nonetheless
remarkably useful (and far more efficient than a database).
Exercises and solutions can be found at www.AtomicKotlin.com.