Learn the Basics of the Kotlin Language

May 22 2024 · Kotlin 1.9, Android 14, Kotlin Playground 1.9

Lesson 05: Learn Null

Demo

Episode complete

Play next episode

Next
Transcript

Any attempt to dereference a null value results in a NullPointerException. To better handle null values, Kotlin provides null-safety. These include a number of mechanisms that ensure better handling of null values and prevents common programming issues related to null values. In this demo, you’ll see how Kotlin handles null values. To start, open a new Kotlin Playground session by visiting https://play.kotlinlang.org. In Kotlin, a variable is said to be nullable if it can hold a null value. To initialize a null value, simply assign null to the variable.

fun main() {
   val items = null
}

When you specify the type, you need to append a question mark ? to it:

fun main() {
   val items: Int? = null
}

Any attempt to dereference a null value will result in an error. Use the null assertion operator (!!):

fun main() {
   val items: Int? = null

   println(items!!)
}

To avoid this, put the code referencing the nullable type in a try-catch block:

fun main() {
   val items: Int? = null

   try {
      println(items!!)
   } catch(e: NullPointerException) {
      println("NullPointerException safely handled :]")
   }
}

Elvis Operator

You could also check if the value is null before working with it:

fun main() {
   val items: Int? = null
   var amount = 25
   if (items != null){
      amount = items
   }
   println("Amount to pay: $amount")
}

However, this code doesn’t look pretty. Use Kotlin’s Elvis operator (?:) to conveniently check variables for nullability before working with them. Here’s how to assign a value to a nullable variable if it’s not null:

fun main() {
   val items: Int? = null
   var amount = items ?: 25
   println("Amount to pay: $amount")
}

The null-safe operator is represented by a question mark followed by a period?.. This ensures that the method call is executed only if the object is not null. Using the null-safe operator, call the plus() method on the apple variable:

fun main() {
   val apple: Int? = null
   val orange: Int = 5

   val total = apple?.plus(orange)
   println(total)
}

Run it. It prints null. Which means when you referenced a null object it didn’t result in an error. Let apple remain nullable, but initialize it with a valid number this time:

fun main() {
   val apple: Int? = 5
   val orange: Int = 5

   val total = apple?.plus(orange)
   println(total)
}

Run the code. This time, the total is 10 because apple was not null.

When dealing with non-nullable and nullable objects, use let to call the method on the non-nullable object instead of the nullable one.

The let function behaves like the null-safe ?. operator. In that, it only executes if the object isn’t null:

fun main() {
   val apple: Int? = 5
   val orange: Int = 5

   val total = apple?.let { orange.plus(it) }
   println(total)
}

Run the code. The output tells you that 5 apples were indeed added to the oranges. By default, it within the plus() function holds the value of apple when it’s not null. In this case, it holds the value 5.

Like ‘let’, ‘run’ uses the ‘this’ object instead of the ‘it’ variable.

When you declare a variable in Kotlin, you’re required to initialize it. For nullable types, you can simply initialize with null. For non-nullable types, Kotlin provides the notNull delegate to handle such situations.

NotNull Delegate

The delegate pattern is a software design pattern in which an object delegates its duties to another object. The notNull delegate allows a variable to be declared as non-nullable, but not during initialization. It must be a mutable variable since the value has to be provided later on in the program.

import kotlin.properties.Delegates

fun main() {
   var items by Delegates.notNull<Int>()

   items = 5
   println(items)
}

Remember that the items variable cannot be assigned a null value. Doing so will result in an error, since you already defined it as non-nullable.

You can use the notNull delegate for primitive data types like Int and String. But, for non-primitive or custom types, you must use the lateinit modifier.

Lateinit Modifier

This modifier is used to initialize a variable later in the program rather than at declaration. It behaves just like Delegate.notNull(). Define and initialize a Book class with a method called display that prints a text:

lateinit var book: Book

fun main(args: Array<String>) {
  book = Book()
  book.display()
}

class Book {
  fun display(){
    println("lateinit modifier works just like Delegate.notNull()")
  }
}

Run the code. See the results in the console:

lateinit modifier works just like Delegate.notNull()

When you’re confident that a nullable type will hold a non-null value, Kotlin gives you access options. If you don’t want to access it without the safe call operator, ?., you can use the null asserting operator, !!.

The Null Assertion Operator

This operator asserts that an object, though nullable, is not null.

fun main() {
   var fruit: String? = null
   fruit = "Salad"
   println(fruit!!.uppercase())
}

This prints SALAD because fruit isn’t null. It contains the word “Salad”.

You have to be careful when using this operator because if your assertion fails, it’ll raise an error in your program.

Nullable Receiver

Some functions are defined on nullable receivers. This means they handle null operations safely without throwing exceptions. A good example is the toString() function. If you call toString() on a null object, it returns a “null” string:

fun main() {
   val items = null
   val result = items.toString()
   println(result::class.java.simpleName)
}

Safe Casts

Casting in programming means converting one data type to another data type. You must ensure that you’re casting the object to the correct type. For instance, you cannot cast an integer to a string, but you can cast an object of type Any to a string if it contains a string value.

To avoid runtime errors, you can use the safe cast operator, as?. It attempts to perform the cast, but instead of throwing an error if it fails, it assigns a null value. Here’s an example of how to use the safe cast operator to cast the variable “items” to an integer:

fun main() {
   val food: Any = "Corn"
   val staple = food as? Int
   println(staple)
}

You got a null because the cast failed. Remove the ? after the as operator, and the cast is no longer safe:

fun main() {
   val food: Any = "Corn"
   val staple = food as Int
   println(staple)
}

Run the code and see the results:

Exception in thread "main" java.lang.ClassCastException: class java.lang.String cannot be cast to class java.lang.Integer (java.lang.String and java.lang.Integer are in module java.base of loader 'bootstrap')
 at FileKt.main (File.kt:3)
 at FileKt.main (File.kt:-1)
 at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0 (:-2)

Nullable Collections

When a collection contains nullable data, it’s important to handle it carefully. Rather than manipulating all items in a null-safe manner, it’s recommended to remove all null values from the collection before use. You can do this by using the filterNotNull() method, which is available on all Collection types.

fun main() {
   val fruits = listOf("Pear", "Mango", null, "Orange")
   println(fruits)

   val nonNullFruits = fruits.filterNotNull()
   println(nonNullFruits)
}

That’s all for this demo. Continue to the final part of this lesson.

See forum comments
Cinema mode Download course materials from Github
Previous: Instruction 3 Next: Conclusion