Why check first
Reading a key that isn't in a map doesn't throw. It gives null, and the type of map[key] is the nullable String? or Int?. That is fine until you add !! to force a plain value: on null it crashes the program with a NullPointerException. So when a key might be missing, ask the map first.
in and !in
On a map, key in map is short for map.containsKey(key). It gives true when the key is there and false when it isn't, so it goes right into an if. !in asks the opposite.
val capitals = mutableMapOf("France" to "Paris", "Japan" to "Tokyo")
println(capitals)
if ("Japan" in capitals) {
println(capitals["Japan"])
}
if ("Spain" !in capitals) {
println("No Spain")
}
{France=Paris, Japan=Tokyo}
Tokyo
No Spain
Printing a map shows each entry as key=value, so capitals["Spain"] would print null, while capitals["Spain"]!! would stop the program.
A value is not a key
The in operator looks only at the keys. "Paris" in capitals is false, because "Paris" is a value. To look through the values, ask for them: "Paris" in capitals.values is true, and capitals.containsValue("Paris") says the same.
A key also has to match exactly, upper and lower case too. "japan" in capitals is false when the key is "Japan".
in or a null test?
capitals["Japan"] != null gives the same answer here, but in says what you mean and it keeps working for a map whose values are allowed to be null. In such a map a key can be present with a null value, and the read can't tell that apart from a missing key. Only in (or containsKey) can.
For a map like capitals whose values are never null, either test is fine, so choose the one that reads best.
Quick reference
For capitals above, the questions and their answers are:
| Expression | Result |
|---|---|
"Japan" in capitals | true |
"Spain" in capitals | false |
"Spain" !in capitals | true |
"Paris" in capitals | false |
"Paris" in capitals.values | true |
"japan" in capitals | false |
A first use: update or create
The check lets you treat known and unknown keys differently. Counting words is the classic case: if the word is already a key, add 1 to its count; otherwise start it at 1.
val counts = mutableMapOf<String, Int>()
for (w in listOf("a", "b", "a")) {
if (w in counts) counts[w] = counts[w]!! + 1 else counts[w] = 1
}
println(counts)
{a=2, b=1}
Here !! is safe, because the if has just proved the key is there. That is the pattern to remember: check with in, then read.
The exercise
pets maps each pet's name to its kind: Rex is a dog, Tom is a cat and Polly is a parrot. Step 1 checks "Tom" in pets and prints "Tom is a " plus pets["Tom"]. Step 2 checks "cat" !in pets. The word "cat" only appears as Tom's kind, which is a value, so it is not a key and the check is true.
val pets = mutableMapOf("Rex" to "dog", "Tom" to "cat", "Polly" to "parrot")
if ("Tom" in pets) {
println("Tom is a " + pets["Tom"])
}
if ("cat" !in pets) {
println("cat is not a key")
}
Tom is a cat
cat is not a key
The string join works without !! because + turns a String? into text. If the key were missing, it would print the word null, which is another reason to check first.
Notice that the two checks are independent. The first if reads a value, so it needs the key to exist; the second only asks a question and never reads anything. Swapping the second check to "cat" in pets would be false for the same reason: the map has no key called "cat", however many animals are cats.
Key takeaways
- A missing key reads as
null, and!!on it crashes. key in mapiscontainsKey;key !in mapis the opposite.inlooks at keys only; usemap.valuesto search values.- Keys must match exactly, including case.
