Mastering Nullability and Collections in Kotlin: Unleash the Full Power of Your Code.
Published: 2023-04-24 20:17:50

 In this article, we're going to look at two important concepts in Kotlin OOP;
_**1. Nullability**_
_**2. Collections**_

##1. Nullability

- NullPointer Exceptions are thrown by the program at runtime and sometimes cause application failure or system crashes.
- NullPointer exceptions are caused by:

1. Explicit call to throw NullPointer()
2. Use of the null assertion operator !!
3. Data inconsistency with regard to initialization
4. Java interoperations eg attempts to access a member on a null reference.
- Kotlin is designed to avoid errors due to null values. The code below demonstrates this design choice;
```
fun main() {
   var message : String = "Hello world"
    message = null
    println(message)
}
```
- When you try to run the above code you will realize that it is not possible to assign a null value to the message variable and the compiler will display an error.
- To allow a variable to hold null, we can declare a variable as nullable string, written as `String?`
```
fun main() {
   var message : String? = "Hello world"
    message = null
    println(message)
}
```
- If you try to access the length property of the message variable while the message variable is null, the exception will be thrown.
```
fun main() {
   var message : String? = "Hello world"
   message = null
   var length = message.length
    println(length)
    
}
```
- To avoid these errors, you will need to check for a null value before accessing the property. Here's the update for the above code in using the length method.
```
fun main() {
   var message : String? = "Hello world"
   message = null
    if (message != null){
        var length = message.length
        println(length)
    }else {
        println(null)
    }    
}
```
- We can deal with nulls in the following ways:

1. **Smart casting** - It is used if you add a condition and it allows for treating a variable as not null after you check that it is not null.
2. **Safe call** - It means calling the right side, if the left side is not null.
3. **Elvis Operator** - It is used to provide a default value when a value could be null.
4. **Not null insertion** - The "Not null assertion" is an unsafe option, as it throws an exception when a value is null.

### 1. Smart - Casting

- Kotlin compiler tracks conditions inside the `if expression`. If the compiler founds a variable is not null or type nullable then the compiler will allow to access the variable.
```
fun main() {
   var message : String? = "Hello world"
    //Smart cast
    if (message != null){
        var length = message.length
        println(length)
    }else {
        println(null)
    }    
}
```
- While using `is` and `!is` for checking the variable the compiler tracks this information and internally cat the variable to the target type.
```
fun main() {
   var message : Any? = "Hello world"
    //Smart cast using is
    if (message is String){
        var length = message.length
        println("The string length is $length")
    }    
}
```
- This is done inside the scope if `is` or `!is` returns true.
- In addition we can use !is for smart cast.
```
fun main() {
   var message : Any? = 67
    //Smart cast using is
    if (message !is String){
      println("Object is not String")
    } else {
         var length = message.length
        println("The string length is $length") 
    }   
}
```
### 2. Not null insertion : !! Operator

- The not null assertion (!!) operator converts any value to non-null type and throws an exception if the value is null.
- If anyone want NullPointer Exception then he can ask explicitly using this operator.
```
fun main() {
   var message : String? = "Meercedes Benz"
    //using the not null assertion
    println(message!!.length)  
    // Using a null 
    // The length method we throw an error
    message = null
    println(message!!.length)
}
```
### 3. Elvis Operator 

- It returns a non- null value  or a default value, when the original variable is null.
- In other words, if left expression is not null then elvis operator returns it, otherwise it returns the right expression.
```
fun main() {
 val str: String? = null
val length = str?.length ?: -1
println("Length of the string is $length")
} 
```
- we can also throw and return expressions on the right side of the elvis operator and it is very useful infunctions. Hence, we can throw an exception instead of returning a default value in theright side of the elvis operator.

`val name = firstname?: throwIllegalArgumentsExceptions("Enter valid name")`

### 4. Safe call

- A safe call is when `?.` is used instead of`. syntax` between an object and its function or property.
- A safe call calls the right side if the left side is not null. Otherwise, It does nothing and returns null.
```
fun main() {
 var str: String? = "Happy Birthday"
    println(str?.length)
    str = null
    println(str?.length)
}
```
The output of the first pritnln will be the length  of our string ,14, the last println will be null.
- We can use the safe call operator with let(), also() and run() if value is not null.

**let() method**

- The lambda expression present inside the let is executed only if the variable firstName is not null. 

`val firstName: String? = null`
`firstName?.let { println(it.toUpperCase()) }`

- Here, the variable firstName is null, so the lambda expression is not executed to convert the string to Upper Case letters.
Kotlin program of using let.
```
fun main(args: Array<String>) {
    // created a list contains names
    var stringlist: List<String?> = listOf("Geeks","for", null, "Geeks")
    // created new list
    var newlist = listOf<String?>()
    for (item in stringlist) {
        // executes only for non-nullable values
        item?.let { newlist = newlist.plus(it) }
    }
    // to print the elements stored in newlist
    for(items in newlist){
        println(items)
    }
}
```

**also() method chain with let()**

- If we want to apply some additional operation like printing the non-nullable items of the list we can use an `also()` method and chain it with a `let()` or `run()`: 
```
fun main(args: Array<String>) {
    // created a list contains names
    var stringlist: List<String?> = listOf("Geeks","for", null, "Geeks")
    // created new list
    var newlist = listOf<String?>()
    for (item in stringlist) {
        // executes only for non-nullable values
        item?.let { newlist = newlist.plus(it) }
        item?.also{it -> println(it)}
    }
}
```
**run() method**

- Kotlin has a run() method to execute some operation on a nullable reference. 
- It seems to be very similar to let() but inside of a function body, the run() method operates only when we use this reference instead of a function parameter: 
```
fun main(args: Array<String>) {
    // created a list contains names
    var stringlist: List<String?> = listOf("Geeks","for", null, "Geeks")
    // created new list
    var newlist = listOf<String?>()
    for (item in stringlist) {
        // executes only for non-nullable values
        item?.run { newlist = newlist.plus(this) } // this reference
        item?.also{it -> println(it)}
    }
}
```

##2. Collections in Kotlin

- A collection usually contains a number of objects of the same type called elements or items.
- In Kotlin collections can be either _mutable_ or _immutable_.
- **Immutable collections** supports read-only functionalities and cannot be modified whereas mutable collections supports both read and write functionalities.
- We will look at several collections which include:

1. Lists
2. Sets
3. Map

#### Lists

- It is an ordered collection in which we can access elements using indices.
- In a list, we can have repeating elements.
```
fun main() {
 var groceries = listOf("carrots","onions","kales")
 println(groceries)
}
```
- The result type is `list<T>` where `T` is the type of the elements in the list. In the code above our lists consists of string hence its type is `list<String>`
- We add elements to alist using the plus sign (+). You can add a single element to a list or you can add two lists together.
```
fun main() {
 var groceries : List<String> = listOf("carrots","onions","kales")
 var numbers : List<Int> = listOf(12,45,78)
 println(groceries + numbers)
}
```
- We can check the number of items in a list using the `size` property.
```
fun main() {
 var groceries : List<String> = listOf("carrots","onions","kales")
 var numbers : List<Int> = listOf(12,45,78)
 val shopping = groceries + numbers
 println(shopping.size)//6
}
```
- We can use the isEmpathy method or compare the size of a list to zero to check if the list is empty.
- You can get an element at a certain position using the index that is the box brackets.
```
fun main() {
 var groceries : List<String> = listOf("carrots","onions","kales")
 var numbers : List<Int> = listOf(12,45,78)
 // using the + adding sign
 val shopping = groceries + numbers
 //using the size property
 println(shopping.size)//6
 // Using the isEmpty() property
 println(shopping.isEmpty())//false
}
```
- We use the `contains method` or `in operator` to check if a set contains a certain element. In the above code we can add the following code.

 `println("carrots" !in list)//true`
` println("apple" in list)// false`

- With mutable lists you can use methods like add or remove to add or remove a certain element.
```
fun main() {
    val mutableList = mutableListOf("apple", "banana", "cherry")
    
    // adding an element to the list
    mutableList.add("date")
    
    // removing an element from the list
    mutableList.remove("banana")
    
    // updating an element in the list
    mutableList[0] = "apricot"
    
    // printing the contents of the list
    println(mutableList)
}
```
#### Set
- It is a collection of unordered elements which are unique. It does not support duplicate elements.
```
fun main() {
    val setList = setOf("apple", "banana", "cherry")
    for (items in setList){
        println(items)
    }
}
```
- With mutable sets you can use methods like add or remove an element. Set preserve the order of the elements.
```
fun main() {
   // create a mutable set
val mutableSet = mutableSetOf<Int>()

// add elements to the mutable set
mutableSet.add(1)
mutableSet.add(2)
mutableSet.add(3)

// remove an element from the mutable set
mutableSet.remove(2)

// check if an element is in the mutable set
val containsOne = mutableSet.contains(1)

// iterate over the mutable set
for (element in mutableSet) {
    println(element)
  }
}
```
#### Map

- Map keys are unique and hold only one value for each key. Each key maps to exactly one value.
- Maps are used to store logical connections between two objects. The values in a  map can be duplicate with unique keys.
```
fun main() {
    // create an immutable map
val immutableMap = mapOf(
    "apple" to 1,
    "banana" to 2,
    "orange" to 3,
    "pear" to 4,
    "grape" to 5
)

// access values in the map
val bananaValue = immutableMap["banana"]

// add a new element to the map
val newMap = immutableMap + ("kiwi" to 6)

// remove an element from the map
val removedMap = immutableMap - "pear"

// iterate over the map using restructuring
for ((key, value) in immutableMap) {
    println("$key -> $value")
 }
}
```
- Maps do not allow duplicates so when you add a new association, it removes the old one.
- You can remove certain elements from a map using the minus sign.
- Kotlin supports the restructuring of a map in a for loop.
```
fun main() {
    // Create a mutable map
    val myMap = mutableMapOf<String, Int>()

    // Add items to the map
    myMap["apple"] = 3
    myMap["banana"] = 5
    myMap["orange"] = 2

    // Print the map
    println("My map: $myMap")

    // Update an item in the map
    myMap["banana"] = 6

    // Print the updated map
    println("My updated map: $myMap")

    // Remove an item from the map
    myMap.remove("orange")

    // Print the updated map
    println("My final map: $myMap")
}
```
- You can add new associations to a map using the bracket and assignment. In addition, we can also remove an association by using the remove method.