Kotlin Notes Help

Null safety

Nullable types and non-nullable types

The only possible causes of an NullPointerException in Kotlin are:

  • An explicit call to throw NullPointerException().

  • Usage of the !! operator that is described below.

  • Data inconsistency with regard to initialization, such as when:

    • An uninitialized this available in a constructor is passed and used somewhere (a "leaking this ").

    • A superclass constructor calls an open member whose implementation in the derived class uses an uninitialized state.

In Kotlin, the type system distinguishes between references that can hold null (nullable references) and those that cannot (non-nullable references).

For example, a regular variable of type String cannot hold null:

// Regular initialization means non-nullable variable by default var a: String = "abc" a = null // compilation error

To allow nulls, you can declare a variable as a nullable string by writing String?:

var b: String? = "abc" // can be set to null b = null // ok print(b)

Now, if you call a method or access a property on a, it's guaranteed not to cause an NPE(because if a is null, then already a compilation has been thrown), so you can safely say:

val l = a.length

But if you want to access the same property on b, that would not be safe, and the compiler reports an error:

val l = b.length // error: variable 'b' can be null

But you still need to access that property, right? There are a few ways to do so.

Checking for null in conditions

First, you can explicitly check whether b is null, and handle the two options separately:

val l = if (b != null) b.length else -1

The compiler tracks the information about the check you performed, and allows the call to length inside the if.

More complex conditions are supported as well:

val b: String? = "Kotlin" if (b != null && b.length > 0) { print("String of length ${b.length}") } else { print("Empty string") }

Safe calls

Your second option for accessing a property on a nullable variable is using the safe call operator ?.:

val a = "Kotlin" val b: String? = null println(b?.length) println(a?.length) // Unnecessary safe call

This returns b.length if b is not null, and null otherwise. The type of this expression is Int?.

Safe calls are useful in chains.

For example, Bob is an employee who may be assigned to a department (or not). That department may in turn have another employee as a department head. To obtain the name of Bob's department head (if there is one), you write the following:

bob?.department?.head?.name

Such a chain returns null if any of the properties in it is null.

To perform a certain operation only for non-null values, you can use the safe call operator together with let:

val listWithNulls: List<String?> = listOf("Kotlin", null) for (item in listWithNulls) { item?.let { println(it) } // prints Kotlin and ignores null }

A safe call can also be placed on the left side of an assignment.

Then, if one of the receivers in the safe calls chain is null, the assignment is skipped and the expression on the right is not evaluated at all:

// If either `person` or `person.department` is null, // the function is not called: person?.department?.head = managersPool.getManager()

Nullable receiver

Extension functions can be defined on a nullable receiver.

This way you can specify behaviour for null values without the need to use null-checking logic at each call-site.

For example, the toString() function is defined on a nullable receiver.

It returns the String "null" (as opposed to a null value). This can be helpful in certain situations, for example, logging:

val person: Person? = null logger.debug(person.toString()) // Logs "null", does not throw an exception

If you want your toString() invocation to return a nullable string, use the safe-call operator ?.:

var timestamp: Instant? = null val isoTimestamp = timestamp?.toString() // Returns a String? object which is `null` if (isoTimestamp == null) { // Handle the case where timestamp was `null` }

Elvis operator

When you have a nullable reference, b, you can say "if b is not null, use it, otherwise use some non-null value":

val l: Int = if (b != null) b.length else -1

Instead of writing the complete if expression, you can also express this with the Elvis operator ?::

val l = b?.length ?: -1

If the expression to the left of ?: is not null, the Elvis operator returns it, otherwise it returns the expression to the right.

Note that the expression on the right-hand side is evaluated only if the left-hand side is null.

Since throw and return are expressions in Kotlin, they can also be used on the right-hand side of the Elvis operator.

This can be handy, for example, when checking function arguments:

fun foo(node: Node): String? { val parent = node.getParent() ?: return null val name = node.getName() ?: throw IllegalArgumentException("name expected") // ... }

The !! operator

The third option is for NPE-lovers: the not-null assertion operator (!!) converts any value to a non-nullable type and throws an exception if the value is null.

You can write b!!, and this will return a non-null value of b (for example, a String in our example) or throw an NPE if b is null:

val l = b!!.length

Thus, if you want an NPE, you can have it, but you have to ask for it explicitly and it won't appear out of the blue.

Safe casts

Regular casts may result in a ClassCastException if the object is not of the target type. Another option is to use safe casts that return null if the attempt was not successful:

val aInt: Int? = a as? Int

When you use a regular cast (e.g., a as Int), you are telling Kotlin to treat the object a as if it is of type Int. If a is not actually an Int, this will cause a ClassCastException and your program will crash.

Collections of a nullable type

If you have a collection of elements of a nullable type and want to filter non-nullable elements, you can do so by using filterNotNull:

val nullableList: List<Int?> = listOf(1, 2, null, 4) val intList: List<Int> = nullableList.filterNotNull()
Last modified: 14 March 2024