Kotlin. Null-safety. General information. Symbol ?. Operators ?. and ?:

Null-safety. General information. Symbol ?. Operators ?. and ?:


Contents


Search other resources:

1. The concept of a null value. Elements that get null-value

In modern programming languages (including Java, C#, etc.) there are situations when a value does not contain any value. The terms null-value and zero-value are different things.

In the Kotlin language, null means that there is no value for variables declared as val or var at the moment. Based on this context, Kotlin language elements are divided into two types:

  • nullable are elements that can take a null value;
  • non-nullable – elements that cannot take a null value.

When writing programs, it is important to use nullable elements correctly to avoid errors. In other programming languages (Java in particular), incorrect use of nullable elements leads to run-time errors, which is very undesirable. As a consequence, an exception is thrown (in Java it is a java.lang.NullPointerException), which is a consequence of the fact that the compiler was unable to detect an error during the compilation of the program.

In the Kotlin language, the type system is organized in such a way that errors using nullable types are already detected at compile time. That is, the error can be immediately identified and corrected, otherwise the program code will not be compiled. This is a significant advantage of the Kotlin language.

 

2. Declaration of variables that support null-value (nullable-variables). Symbol ?

Unlike the Java language (as well as other languages), Kotlin allows the programmer to directly specify nullable types. This is done by adding the symbol ? (question mark) to type name

Type?

where

  • Type – any basic type of the Kotlin language (Int, Double, Byte, Short, UByte, Char, String and others).

If a variable is declared as a nullable variable, then it can be assigned the null value.

Example.

The example demonstrates the declaration and use of nullable variables of different types.

fun main(args: Array<String>) {

  var i1 : Int? = null // nullable-variable of Int type
  val d1 : Double? = null // nullable-variable of Double type
  val s1 : String? = null // nullable-variable of String type
  var b1 : Boolean? = null // nullable-variable of Boolean type
  var c1 : Char? = null // nullable-variable of Char type

  println(i1)
  println(d1)
  println(s1)
  println(b1)

  // nullable variables declared as var can be assigned other values
  i1 = 25; println(i1);
  b1 = true; println(b1);
  c1 = '+'; println(c1);
}

Result

null
null
25
true
+

 

3. Ensuring null safety in the Kotlin language

If the program declares a type value with null support, then at the compilation stage, a check for null-valid values will be performed. Thus, the range of possible operations will be narrowed. If the operation is not null-safe, an error message will be displayed. Below are possible cases of such errors and attempts to eliminate them.

 

3.1. Return the result from function, which may be null

For example, the following code will generate a compilation error:

// A function that returns Int from null-string
fun GetLenString(str : String?) : Int {
  return str.length // error
}

If you try to compile this function, you will get an error

Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type String?

Indeed, how can you return the length of a string of type String?, which can be null, that is, not exist.

To fix the situation, you need to enter a null-checking code like

// A function that returns an Int from a null string
fun GetLenString(str : String?) : Int {
  if (str!=null) // check for null
    return str.length
  else
    return 0
}

 

3.2. Assigning a nullable value to a variable that does not support null

If a variable is declared that does not support a null value, then an attempt to assign the value of a nullable variable to this variable will cause a compilation error.

For example, in the following code

fun main(args: Array<String>) {

  // 1. Variable supporting null
  var X : Double? = 23.8

  // 2. Variable not supporting null
  var Y : Double = 33.1

  // 3. Assignment attempt
  Y = X // error
}

when trying to compile in the assignment string

Y = X

the compiler will throw an error

Type mismatch: inferred type is Double? but Double was expected

To prevent the compiler from throwing an error, you need to implement a check for null, for example, yes

fun main(args: Array<String>) {
  // 1. Variable supporting null
  var X : Double? = 23.8

  // 2. Variable not supporting null
  var Y : Double = 33.1

  // 3. Assignment attempt, check for null
  if (X!=null)
    Y = X // it works, no error
  else
    Y = 0.0
}

 

3.3. Passing a null parameter to a function that declares a non-nullable parameter

If a function is declared that takes a non-nullable parameter, then an attempt to pass a nullable value to that function will fail.

For example, in the code below

// A function that receives a value that does not support null
fun ShowInteger(num : Int) : Unit {
  println("num = " + num)
} 

fun main(args: Array<String>) {
  var num1:Int = 10 // non-nullable type
  var num2:Int? = 20 // nullable-type
  ShowInteger(num1); // it works
  ShowInteger(num2) // here is an error
}

compiler in the line

ShowInteger(num2)

will generate an error

Type mismatch: inferred type is Int? but Int was expected

To fix the error, the ShowInteger() function must receive the nullable type Int?. As a result, the function code might look something like this

// A function that receives a value that does not support null
fun ShowInteger(num : Int?) : Unit {
  // Checking for null
  if (num!=null)
    println("num = " + num)
}

 

4. The safe call operator ?.. Example

With a secure call operator ?. you can implement a check for null and a call method. In the most general case, the use of the operator ?. is as follows

obj?.MethodName()

here

  • obj – the variable name (object);
  • MethodName() – the name of the function (method) that is called from the instance (object) obj. Calling MethodName() can be with passing parameters to it.

The obj?.MethodName() call code is converted by the compiler into code that contains a null check

if (obj!=null)
  obj.MethodName()
else
  null

Figure 1 shows the operation of the ?. operator.

Kotlin. Null-safety. Operator ?.

Figure 1. Operator ?.

Example.

The example uses a modified GetLengthString() function that uses the ?. operator.

// A function that returns a nullable String? value
fun GetLengthString(s : String?) : Int? {
  return s?.length // operator ?.
}

fun main(args: Array<String>) {
  // Using the ?. operator to get the length of a null string
  var s : String? = "abcde"
  println(GetLengthString(s))
  s = null
  println(GetLengthString(s))
}

Result

5
null

 

5. Default null assignment operator ?: (Elvis)

The ?: operator implements a null check based on two operands. This operator is also called the null coalescing operator. In the most general case, the use of the ?: operator is of the form

op1 ?: op2

here op1, op2 are some operands involved in obtaining the result. The result of the operation is formed based on Figure 2.

Kotlin. Null-safety. Operator ?:

Figure 2. Operator ?:

Example.

fun main(args: Array<String>) {
  // Using operator ?:

  // 1. For String type
  var s1 : String? = "abcd"

  var s2 : String = s1 ?: "" // s2 = "abcd"
  println(s2)

  s1 = null
  s2 = s1 ?: "null value" // s2 = "null value"
  println(s2)

  // 2. For Int type
  var i1 : Int? = 25
  var i2 : Int = i1 ?: 0 // i2 = 25
  println(i2)
  i1 = null
  i2 = i1 ?: 0 // i2 = 0
  println(i2)
}

Result

abcd
null value
25
0

 

6. Check for null. Operator !!

With the operator !! you can cast to a type that does not support null. If the value itself is null, then Kotlin will throw an exception. Using the !! operator looks like this

obj!!

here obj – some variable (object).

Operator !! work shown in Figure 3.

Kotlin. Null-safety. Operator !!

Figure 3. Operator !!

Example 1. An exception is thrown for type String.

fun main(args: Array<String>) {
  // Operator !!

  // Using for String type
  var s1 : String? = "abcd"
  var s2 : String = s1!!   // s2 = "abcd"
  println(s2)

  s1 = null
  s2 = s1!!   // exception NullPointerException
}

Result

abcd
Exception in thread "main" kotlin.KotlinNullPointerException
at TrainKt.main(train.kt:16)

Example 2. Throws an exception for the Int type.

fun main(args: Array<String>) {
  // Operator !!
  // Using for Int type
  var i1 : Int? = 25
  var i2 : Int
  i2 = i1!!   // i2 = 25
  println(i2)

  i1 = null
  i2 = i1!!   // The NullPointerException is generated
  println(i2)
}

Result

25
Exception in thread "main" kotlin.KotlinNullPointerException
at TrainKt.main(train.kt:13)

 

7. Function let

The let function is used for nullable lambda expressions. This function is combined with the ?. operator used to conveniently write nullable expressions.

The general form of using the function is as follows

obj2 = obj1?.let {
  // code of lambda expression
  // using it
  // ...
}

here

  • obj1, obj2 – variables (objects) of nullable-type.

Figure 4 shows how the let function works.

Kotlin. Null-safety. Function let. Safe invocation of a lambda expression

Figure 4. Function let. Safe invocation of a lambda expression

As you can see from the figure, if obj!=null, then the lambda expression is executed. If obj==null, then nothing happens and the lambda expression is not executed. As the size of the evaluated expression increases, the effect of using the let function increases significantly.

Example.

fun main(args: Array<String>) {
  // The let function in combination with ?.

  // 1. For String type
  // 1.1. nullable-variables are declared
  var s1 : String?
  var s2 : String?

  // 1.2. Not null-value
  s1 = "abcd"

  s2 = s1?.let { // Lambda-expression
    it.reversed() // String reversing
  }
  println(s2) // "dcba"

  // 1.3. null-value
  s1 = null
  s2 = s1?.let {
    it.reversed()
  }
  println(s2) // null

  // 2. Using with Double-type
  // 2.1. Declare two nullable-variables
  var d1 : Double?
  var d2 : Double?

  // 2.2. Not null-value
  d1 = 2.33
  d2 = d1?.let { // multiply by 3
    it * 3
  }
  println(d2) // 6.99

  // 2.3. null-value
  d1 = null
  d2 = d1?.let {
    it * 3
  }
  println(d2) // null
}

Result

dcba
null
6.99
null

 


Related topics