The code below is a function that converts the name variable passed as a parameter to uppercase and outputs it.
It seems to run without errors, but an error occurs at compile time.
fun printName(name: String?){
println(name.toUpperCase())
}
The error message is:
Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type String?
The cause of the error is that the name parameter is a nullable type that can be null.
If you call toUpperCase() of a nullable object, NullPointerException is thrown, so it is judged as unsafe code and an error has occurred.
As in the code below, if the name variable passed as a parameter is a non-nullable type that cannot contain null, no error occurs because null handling is not required.
fun printName(name: String){
println(name.toUpperCase())
}
kotlin distinguishes between nullable types that can contain null and non-nullable variables that do not contain null, so that if null can be included, NullPointException must be handled so that NullPointException does not occur.
Next, let's see how to safely handle null in kotlin
Using the non-null assertion operator may result in a NullPointerException.
The non-null assertion operator asserts that a variable is not null and leaves it to the developer's judgment whether it is null or not.
Developers should only use it if it is certain that it is not null, otherwise a KotlinNullPointerException will be thrown.
The code below compiles without errors, but if a null value is passed as an argument, a KotlinNullPointerException is thrown.
fun printName2(name: String?){
println(name!!.toUpperCase())
}
Exception in thread "main" kotlin.KotlinNullPointerException
at com.devez.NullSafeKt.printName2(NullSafe.kt:61)
at com.devez.NullSafeKt.test1(NullSafe.kt:34)
at com.devez.NullSafeKt.main(NullSafe.kt:4)
at com.devez.NullSafeKt.main(NullSafe.kt)
If null is checked with a conditional statement, it is smart cast to a non-nullable type within the conditional block.
fun printNameIf(name: String?){
if(name != null){
println(name.toUpperCase())
}
}
Note that if you use a code that checks for a null condition on a variable that can be changed, a compilation error occurs.
var name:String? = null
fun printNameIf(){
if(name != null){
println(name.toUpperCase())
}
}
The error message is as follows, and the cause of the error is that the conditional statement is processed in a multi-threaded environment and control is transferred to another thread to modify the name variable to a null value.
In this case, even in the block of conditional statements, the name variable can be null, which causes a compilation error.
Smart cast to 'String' is impossible, because 'name' is a mutable property that could have been changed by this time
This error also occurs when null checking a property of a class.
fun printPersonNameIf(person: Person?){
if(person != null && name != null){
println(name.toUpperCase())
}
}
Since the person object is a val variable, null check is possible with a conditional statement, but if the property is a var type, a compilation error occurs.
If the safe call operator is used, the toUpperCase method is called only if it is not null.
If it is null, the toUpperCase method is treated as a null value without calling.
fun printNameSafeCall(name: String?){
println(name?.toUpperCase())
}
Safe call operators can be chained and used consecutively.
fun printNameSafeCall(person: Person?){
println(person?.name?.toUpperCase())
}
You can safely handle null by using the let function on a nullable type.
var name:String? = null
fun printNameLet(){
name?.let {
println(it.toUpperCase())
}
}
If it is not null, the left operand is called; if it is null, the right operand is called.
fun printNameLetElvis(name: String?){
println(name?.toUpperCase() ?: "Unknown")
}