Welcome to our exploration of Compound Data Structures in Kotlin. Having navigated through Maps
, Sets
, and Lists
, we'll delve into nested maps
and lists
. These structures enable us to handle complex and hierarchical data, which is typical in real-world scenarios. This lesson will guide you through a recap of the basics, the creation and modification of nested maps and lists, as well as common error handling.
Here's a simple example of a school directory that uses a map with grades as keys and lists of students as values:
Kotlin1fun main() { 2 // Map with grades as keys and lists of students as values 3 val schoolDirectory = mapOf( 4 "Grade1" to listOf("Amy", "Bobby", "Charlie"), 5 "Grade2" to listOf("David", "Eve", "Frank"), 6 "Grade3" to listOf("George", "Hannah", "Ivy") 7 ) 8 9 // Prints the Grade1 list in the map 10 println(schoolDirectory["Grade1"]) // Output: [Amy, Bobby, Charlie] 11}
Just like their non-nested versions, creating nested structures is straightforward. Kotlin's data classes can be leveraged for better organization when needed.
Nested Map:
Kotlin1fun main() { 2 // Map within a map 3 val nestedMap = mapOf( 4 "fruit" to mapOf( 5 "apple" to "red", 6 "banana" to "yellow" 7 ), 8 "vegetable" to mapOf( 9 "carrot" to "orange", 10 "spinach" to "green" 11 ) 12 ) 13 14 // Prints the nested map 15 println(nestedMap) 16 // Output: {fruit={apple=red, banana=yellow}, vegetable={carrot=orange, spinach=green}} 17}
Nested List:
Kotlin1fun main() { 2 // Lists within a list 3 val nestedList = listOf( 4 listOf(1, 2, 3), // inner list within the outer list 5 listOf(4, 5, 6), // another inner list within the outer list 6 listOf(7, 8, 9) // third inner list within the outer list 7 ) 8 9 // Prints the nested list 10 println(nestedList) // Output: [[1, 2, 3], [4, 5, 6], [7, 8, 9]] 11}
Lists within a Map:
Kotlin1fun main() { 2 // Lists within a map 3 val listMap = mapOf( 4 "numbers" to listOf(1, 2, 3), 5 "letters" to listOf("a", "b", "c") 6 ) 7 8 // Prints the map of lists 9 println(listMap) // Output: {numbers=[1, 2, 3], letters=[a, b, c]} 10}
The retrieval of values from nested maps
or lists follows rules similar to those for their non-nested counterparts.
From Nested Map:
Kotlin1fun main() { 2 val nestedMap = mapOf( 3 "fruit" to mapOf( 4 "apple" to "red", 5 "banana" to "yellow" 6 ), 7 "vegetable" to mapOf( 8 "carrot" to "orange", 9 "spinach" to "green" 10 ) 11 ) 12 13 // Accessing apple's color from nested map 14 println(nestedMap["fruit"]?.get("apple")) // Output: red 15}
The notation used to access values from a nested map involves chaining the access operations. Here's a breakdown:
-
Accessing the Outer Map:
nestedMap["fruit"]
: This accesses the value associated with the key"fruit"
in the outer map. The value is another map (mapOf("apple" to "red", "banana" to "yellow")
).
-
Safe Call Operator (
?.
):?.
is used to safely access the inner map. If"fruit"
does not exist innestedMap
, the expression evaluates tonull
, preventing a null pointer exception.
-
Accessing the Inner Map:
.get("apple")
: This accesses the value associated with the key"apple"
in the inner map. The value is"red"
, which is then printed.
Overall, println(nestedMap["fruit"]?.get("apple"))
safely accesses and prints the color of the apple from the nested map structure, outputting "red".
From Nested List:
Kotlin1fun main() { 2 val nestedList = listOf( 3 listOf(1, 2, 3), 4 listOf(4, 5, 6), 5 listOf(7, 8, 9) 6 ) 7 8 // Accessing the 3rd value from the 2nd list in nested list 9 println(nestedList[1][2]) // Output: 6 10}
From Both:
Kotlin1fun main() { 2 val listMap = mapOf( 3 "numbers" to listOf(1, 2, 3), 4 "letters" to listOf("a", "b", "c") 5 ) 6 7 // Accessing the second letter in the 'letters' list in listMap 8 println(listMap["letters"]?.get(1)) // Output: b 9}
The modification of nested lists
and maps
can be done using mutable structures; Kotlin provides mutable variants for altering data.
Kotlin1fun main() { 2 val nestedMap = mutableMapOf( 3 "vegetable" to mutableMapOf( 4 "carrot" to "orange", 5 "spinach" to "green" 6 ) 7 ) 8 val nestedList = mutableListOf( 9 mutableListOf(1, 2, 3), 10 mutableListOf(4, 5, 6), 11 mutableListOf(7, 8, 9) 12 ) 13 14 // Modifying spinach's color to red 15 nestedMap["vegetable"]?.set("spinach", "red") 16 17 // Adding 10 to the first list in nested list 18 nestedList[0].add(10) 19 20 // Adding cherry to the 'fruit' map in nestedMap 21 nestedMap["fruit"] = mutableMapOf("cherry" to "red") 22 23 // Deleting the 2nd value from the 3rd list in nested list 24 nestedList[2].removeAt(1) 25 26 // Deleting carrot from the 'vegetable' map in nestedMap 27 nestedMap["vegetable"]?.remove("carrot") 28}
Kotlin provides try
/catch
blocks for error handling. Below is an example of handling potential null pointer exceptions when accessing nested collections.
Kotlin1fun main() { 2 val nestedMap = mapOf( 3 "fruit" to mapOf( 4 "apple" to "red" 5 ) 6 ) 7 8 // Trying to print a non-existent key in nestedMap 9 try { 10 val color = nestedMap["fruit"]?.get("mango") ?: throw NoSuchElementException("Key not found!") 11 println(color) 12 } catch (e: NoSuchElementException) { 13 println(e.message) 14 } 15}
In this code block, the try
/catch
mechanism is used to handle potential exceptions gracefully when accessing a non-existent key in a nested map. Here's how it works:
-
try
Block:- The
try
block encloses the code that might throw an exception. - In this case,
nestedMap["fruit"]?.get("mango")
attempts to access the color of "mango" from the inner map associated with the "fruit" key. - Because "mango" does not exist in the map, the expression evaluates to
null
.
- The
-
Elvis Operator (
?:
):?:
is used to handle the case where the expression on the left isnull
.- If the result of
nestedMap["fruit"]?.get("mango")
isnull
, the Elvis operator triggers the throwing of aNoSuchElementException
with the message "Key not found!".
-
catch
Block:- The
catch
block captures theNoSuchElementException
if it is thrown in thetry
block. - Within the
catch
block,println(e.message)
prints the exception message "Key not found!" to the console.
- The
Alternatively, you can use Kotlin's let
function combined with the Elvis operator for more elegant error handling:
Kotlin1fun main() { 2 val nestedMap = mapOf( 3 "fruit" to mapOf( 4 "apple" to "red" 5 ) 6 ) 7 8 // Attempting to access a non-existent key with error handling using `let` 9 val color = nestedMap["fruit"]?.get("apple")?.let { 10 println("Apple color found: $it") 11 it 12 } ?: run { 13 // Executed if `let` didn't find a non-null value 14 println("Key not found! Defaulting to unknown color.") 15 "unknown" 16 } 17 18 println("Final color value is $color") 19}
In this second example, let
is employed to safely operate on the non-null color value. If the color is found, it prints a success message and returns the color. If the value is null, the Elvis operator with run
provides a fallback, printing an error message and returning a default value. This approach offers a functional way to handle potential null values while maintaining code readability.
This use of error handling allows the program to avoid crashing due to accessing non-existent keys and provides user-friendly feedback instead.
Bravo! You've made a journey through nested lists
and maps
, essential concepts in the data-intensive programming world. We've learned how to create, access, and modify values in these complex structures and how to handle errors.
Up next, we have hands-on practice sessions to solidify your understanding of these concepts. Hold on to your hats!