Nullable variables |
In Java and Scala, a variable of type \(T\) either contains a reference to an object of the correct type, or the special value null. If the value is null, this means the variable currently does not reference any object.
Programmers use null as a special marker, for instance to indicate that an error occurred, or that some requested information could not be found. This leads to problems when subsequently the code does not check for this special case, because calling any operation on a variable with value null will fail. Since the variable references no object, it is impossible to call any method! The result is a NullPointerException, a bug that is often hard to find.
Kotlin helps us avoid this problem by not allowing null in variables of type Int, String, etc. If you try to set a variable to null, the compiler complains:
>>> val s: String = null error: null can not be a value of a non-null type kotlin.String
Sometimes, however, you really want to allow null, either because you want to use null to indicate a special case or an error, or because you are calling some Java functions that use null. In this case, you need to indicate that the variable is nullable by placing a question mark after the type:
>>> var s: String? = null >>> println(s) null >>> s = "Hello World" >>> println(s) Hello World >>> s = null >>> println(s) null >>> s = "I'm nullable" >>> println(s) I'm nullableSince the type of s is String?, it is allowed to have the value null.
However, whenever we want to call a String method of the object s, we have to be careful: Calling a method would fail if s == null. Therefore, Kotlin forbids calling methods on nullable variables without checking for null first:
>>> s.length error: only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type kotlin.String?
We can test for null manually:
>>> fun printlen(s: String?) { ... if (s == null) ... println("Null string") ... else ... println(s.length) ... } >>> printlen("Hello") 5 >>> printlen(null) Null stringNote that the compiler recognizes that in the else part the value of s cannot be null, and so calling s.length is okay.
Kotlin provides some nice shortcuts to make dealing with nullable variables easier. First, we can use the ?. operator. It calls the method if an object exists, otherwise the method is not called and the result is null:
>>> s I'm nullable >>> s?.length 12 >>> s = null >>> s?.length nullIf we don't like null as the value being returned, we can change that using the "Elvis operator" ?:. It returns its left side if it is not null, otherwise the right side. We can now rewrite the function printlen from above as follows:
>>> fun printlen(s: String?) { ... println(s?.length ?: "Null string") ... } >>> printlen("Hello world") 11 >>> printlen(null) Null string
Finally, sometimes you have a variable of type String?, but you know (because of the documentation or because it's your own code that you analysed carefully) that the variable will never be null. In that case you can promise the compiler that it's okay:
>>> val s: String? = "Hello World" >>> val t: String = s error: type mismatch: inferred type is kotlin.String? but kotlin.String was expected >>> val t : String = s!!The first assignment fails, because s is of type String? and therefore could be null, so the assignment to t (of type String) is not allowed. In the second assignment I use the !! operator to promise the compiler that all is well.
The !! operator can also be used to call a method when you are confident the variable is not null:
>>> s.length error: only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type kotlin.String? >>> s!!.length 11If your promise is false and s is actually null, then an exception will occur at this point:
>>> var s: String? = null >>> s!!.length kotlin.KotlinNullPointerException
One example of a standard Kotlin function that returns a nullable type is readLine(): It returns String?, namely either the input string, or null when the input has ended (for instance because you are redirecting the input from a file).
The following short script shows this (reverse.kts):
fun reverser() { var line: String? = readLine() while (line != null) { println(line.reversed()) line = readLine() } } println("Enter lines to be reversed:") reverser()
If we run the script while redirect the input from the script file itself, it stops correctly after the last line:
$ kts reverse.kts < reverse.kts Enter lines to be reversed: { )(resrever nuf )(eniLdaer = ?gnirtS :enil rav { )llun =! enil( elihw ))(desrever.enil(nltnirp )(eniLdaer = enil } } )":desrever eb ot senil retnE"(nltnirp )(resrever
Nullable variables |