Kotlin Null Safety
Kotlin has a powerful type system that helps developers avoid one of the most common problems in programming: Null Pointer Exceptions. In many languages like Java, trying to use a variable that holds null can cause a NullPointerException (NPE), which can crash your application if not handled properly. This kind of bug has cost time and money in the software industry, which is why it's sometimes called the "billion-dollar mistake." To prevent this, Kotlin's compiler makes you clearly specify whether a variable can be null or not. If you try to use a variable that could be null without checking it, Kotlin won't let you compile the code. However, Kotlin can still throw a NullPointerException in a few specific cases.
When Can Kotlin Throw a NullPointerException?
Even though Kotlin is made to prevent Null Pointer Exceptions, it might still be thrown in these cases:
- When you explicitly throw a NullPointerException().
- If you use the !! operator on a variable that turns out to be null.
- If you try to use an object before it has been properly initialized.
- When you use Java code from Kotlin, which doesn't have the same null safety rules.
Nullable and Non-Nullable Types in Kotlin
Kotlin type system has distinguish two types of references that can hold null (nullable references) and those that can not (non-null references).
A variable of type String can not hold null. If we try to assign null to the variable, it gives compiler error.
var s1: String = "Geeks"
s1 = null // This causes a compile-time error
The code above will not compile because s1 is declared as a non-nullable String. If you want a variable to hold a null value, you must explicitly mark it as nullable using a question mark ? after the type.
var s2: String? = "GeeksforGeeks"
s2 = null // This is allowed
println(s2)
Now that s2 is a nullable string (String?), it can safely hold either a string or null.
When you're working with non-nullable variables, you can safely access their properties without any issues:
val length = s1.length
However, if you try to access properties of a nullable variable like this:
val length = s2.length
The compiler will show an error, because s2 might be null. You need to handle this case safely.
Kotlin program of non-nullable type:
fun main(){
// variable is declared as non-nullable
var s1 : String = "Geeks"
//s1 = null // gives compiler error
print("The length of string s1 is: "+s1.length)
}
Output:
The length of string s1 is: 5
Here, if we try to assign null to a non-nullable variable then it gives compiler time error. But, if we try to access the length of the string then it guarantees not to throw NullPointerException.
Kotlin program of nullable type:
fun main() {
// variable is declared as nullable
var s2: String? = "GeeksforGeeks"
s2 = null // no compiler error
println(s2.length) // compiler error because string can be null
}
Output:
Error:(8, 15) Kotlin: Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type String?
Here, we can easily assign null to a nullable type variable. But we should use the safe operator to get the length of the string.
Checking for Null with if Statement
The most common way of checking null reference is using if-else expression. We can explicitly check if variable is null, and handle the two options separately.
Kotlin program of checking null in conditions:
fun main() {
// variable declared as nullable
var s: String? = "GeeksforGeeks"
println(s)
if (s != null) {
println("String of length ${s.length}")
} else {
println("Null string")
}
// assign null
s = null
println(s)
if (s != null) {
println("String of length ${s.length}")
} else {
println("Null String")
}
}
Output:
GeeksforGeeks
String of length 13
null
Null String
Note that we have used if-else block to check the nullability. If string contains null then it executes the if block else it executes the else block.
Safe Call operator(?.)
Using if statements can become repetitive. Kotlin offers a special operator called the safe call operator, written as ?.. This operator lets you access properties or call functions only if the object is not null. If it’s null, the whole expression simply returns null.
The following expression:
firstName?.toUpperCase()
is equivalent to:
if(firstName != null) firstName.toUpperCase()
else null
Kotlin program of using safe operator:
fun main() {
var firstName: String? = "Praveen"
var lastName: String? = null
println(firstName?.toUpperCase()) // Prints "PRAVEEN"
println(firstName?.length) // Prints 7
println(lastName?.toUpperCase()) // Prints null
}
Output:
PRAVEEN
7
null
let() Function with Safe Calls
Kotlin's let function works nicely with safe calls. You can use it to perform an operation only if the value is not null.
val firstName: String? = null
firstName?.let {
println(it.toUpperCase())
}
Since firstName is null, the block inside let will not run.
Kotlin program of using let:
fun main() {
// 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)
}
}
Output:
Geeks
for
Geeks
Using also() with Safe Calls
Sometimes you want to do something with the value like printing it out before processing. The also() function is useful here. You can combine it with let() or use it on its own.
fun main() {
// 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)}
}
}
Output:
Geeks
for
Geeks
run() Function for Nullable Variables
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() {
// 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)}
}
}
Output:
Geeks
for
Geeks
Elvis Operator(?:)
The Elvis operator is used to return 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. The right-hand side expression is evaluated only if the left-hand side found to be null.
The following expression:
val name = firstName ?: "Unknown"
is equivalent to:
val name = if(firstName!= null)
firstName
else
"Unknown"
Moreover, we can also use throw and return expressions on the right side of Elvis operator and it is very useful in functions. Hence, we can throw an exception instead of returning a default value in the right side of Elvis operator.
val name = firstName ?: throw IllegalArgumentException("Name cannot be null")
Kotlin program of using Elvis operator:
fun main() {
var str : String? = "GeeksforGeeks"
println(str?.length)
str = null
println(str?.length ?: "-1")
}
Output:
13
-1
Not-Null Assertion Operator !!
If you're absolutely sure that a value is not null, you can use the !! operator. However, use it carefully. If the value is null, this will throw a KotlinNullPointerException.
Kotlin program of using Not-Null operator:
fun main() {
var str: String? = "GeeksforGeeks"
println(str!!.length) // OK
str = null
println(str!!.length) // Throws exception
}
Output:
13
Exception in thread "main" kotlin.KotlinNullPointerException at FirstappKt.main(firstapp.kt:8)