DEV Community

Daniel Brian
Daniel Brian

Posted on

Mastering Nullability and Collections in Kotlin: Unleash the Full Power of Your Code.

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.
  5. 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)
}
Enter fullscreen mode Exit fullscreen mode
  • 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)
}
Enter fullscreen mode Exit fullscreen mode
  • 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)

}
Enter fullscreen mode Exit fullscreen mode
  • 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)
    }    
}
Enter fullscreen mode Exit fullscreen mode
  • 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)
    }    
}
Enter fullscreen mode Exit fullscreen mode
  • 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")
    }    
}
Enter fullscreen mode Exit fullscreen mode
  • 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") 
    }   
}
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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")
} 
Enter fullscreen mode Exit fullscreen mode
  • 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)
}
Enter fullscreen mode Exit fullscreen mode

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)
    }
}
Enter fullscreen mode Exit fullscreen mode

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)}
    }
}
Enter fullscreen mode Exit fullscreen mode

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)}
    }
}
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode
  • 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)
}
Enter fullscreen mode Exit fullscreen mode
  • 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
}
Enter fullscreen mode Exit fullscreen mode
  • 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
}
Enter fullscreen mode Exit fullscreen mode
  • 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)
}
Enter fullscreen mode Exit fullscreen mode

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)
    }
}
Enter fullscreen mode Exit fullscreen mode
  • 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)
  }
}
Enter fullscreen mode Exit fullscreen mode

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")
 }
}
Enter fullscreen mode Exit fullscreen mode
  • 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")
}
Enter fullscreen mode Exit fullscreen mode
  • 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.

Top comments (0)