Instruction 3

Working With the Null Assertion Operator

This operator asserts that an object, though nullable, isn’t null. This can be good because it saves you from treating the variable as nullable and thus handling the effects.

In the following example, you’ll use !! to access methods on the fruit object. You’ll do this since you’re confident it contains a valid non-null value:

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

This prints SALAD because fruit isn’t null but 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.

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

Run the above code, and you you’ll get the following error:

Exception in thread "main" java.lang.NullPointerException
at FileKt.main (File.kt:8)
at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0 (:-2)
at jdk.internal.reflect.NativeMethodAccessorImpl.invoke (:-1)

This is also dangerous because you don’t get a compile time error with this one. You told Kotlin you were certain about fruit being non-null. So you asked Kotlin to drop its null-safety mechanisms for fruit. So, the onus was on you to ensure that fruit was indeed not null at the point you called the method on it.

Using the 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)
}

Run this code:

String

The output tells you that the type of the result is a String and not a null.

Working With Safe Casts

To cast one type to another type is to convert one type to another type. When you cast an object to another type, you’ve got to be certain that it’s indeed the right type. You can’t cast an Int to a String. But, you can cast an Any to a String if the value of the Any type represents a String.

Since casting requires that you get it correct, otherwise your program fails, you have to use a safe cast. A safe cast tries to perform the cast. But if it fails, it assigns a null value instead of throwing an error. The safe cast operator is denoted by as?.

Update your example to safely cast items to an Int:

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

Run the code and see the results:

null

You got a null because the cast failed. Remove the ? after the as, 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:10)
at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0 (:-2)
at jdk.internal.reflect.NativeMethodAccessorImpl.invoke (:-1)

Using Nullable Collections

When collections contain nullable data, you have to be careful how you use it. Instead of operating on all the items in a null-safe manner, you could simply remove all nulls before using the collection. You’ll achieve this using the filterNotNull() method. filterNotNull() is available on all Collection types:

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

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

Run the code. The first print statement shows that fruits has a null value in the list. Next, the code filters out the null values with filterNotNull. Finally, it assigns the filtered list to nonNullFruits. The second print statement shows the new list, nonNullFruits. You can see that the new list doesn’t contain any null values.

See forum comments
Download course materials from Github
Previous: Instruction 2 Next: Demo