Open up the starter project to get started.
There’s an input variable produced by the firstOrNull() method call. So, as the name suggests, it may be null. Its type is String?. It is not present in the code, because Kotlin can infer the type from the context. Try adding the type explicitly and run the project with the 2 as an input.
val input: String? = args.firstOrNull()
Now, change the type to non-nullable String and see what happens.
val input: String = args.firstOrNull()
The Kotlin compiler complains that the type mismatch.
It means that you can’t assign an expression of nullable type to a variable of non-nullable type. A compiler won’t let you do that. You can use a non-nullable type if you replace a firstOrNull() method with a first().
val input: String = args.first()
But what if the args array is empty, so there are no arguments given? Remove 2 from the input and run the project again.
The program runs, but it shows nothing as the number of students. Is the nullability bypassed? Have you just tricked the Kotlin compiler and were able to assign a null value to a non-nullable type? No! Examine the input variable by printing its length to the console.
println("Input length: ${input.length}")
The input is an empty string. It isn’t null. Due to the limitations of the Kotlin playground website, you can’t specify the lack of arguments. There’s only one input field, and it becomes the first argument.
To achieve the lack of arguments, remove the argument from the main() method declaration and code an empty string array called args at the very beginning of the method body, then run the snippet.
fun main() {
val args = emptyArray<String>()
val input: String = args.first()
}
Now, the program crashes with an NoSuchElementException at the first() method invocation.
You may ask why there are two methods for the same action. The method having a name without a suffix throws an exception, so it looks like it isn’t safe to use. Why did the Kotlin standard library authors decide to create it? A firstOrNull() method is safer. That’s true. But, it forces developers to write more code to handle the null values, so it is also less efficient.
In many cases, the arrays come from trusted sources. For example, a code written by the same team, which follows the same contracts and requirements. Or, sometimes it does not make sense to handle the null values gracefully. Imagine a program that takes the janitor’s name as an argument and prints the number of classrooms he has cleaned. If the janitor’s name is not given, there is nothing to do and throwing an exception is a desired behavior.
Note: Use APIs having
orNullsuffix, which return nullable types when you expect the absence of data to be a common case — especially when dealing with the user input or the data from the external untrusted sources.
Now you can write a code that handles the null values. Start with extracting the number of students from the input.
val nullableNumber = input?.toIntOrNull()
println("Nullable number of students: $nullableNumber")
The source is untrusted — it’s an input taken from the user. So, you should use the toIntOrNull() function.
It returns null if the first argument of an input isn’t a number. The ? operator is the safe call. You need it here because the intput variable itself may be null when the args array is empty.
The toIntOrNull() function is called only when the input isn’t null. If it turns out to be null, then the result of the whole expression is null without calling the function. The type of the nullableNumber variable is Int?. It’s a nullable type.
Imagine that your program should always print the number of students. If the input is not a number, then it should fall back to zero. You can use the Elvis operator to achieve that.
val numberWithElvis = input?.toIntOrNull() ?: 0
println("Number of students with elvis operator: $numberWithElvis")
The Elvis operator is a binary operator. It has two operands. It returns the left-hand operand
if it is not null, otherwise, it returns the right-hand operand.
It’s a shorthand for the when or if expressions. It’s a very useful operator when you want to provide a default value for a
nullable variables.
The type of right-hand operand doesn’t have to be the same as the left-hand operand. Like in a when expression, it can be also Nothing. You can throw an exception from the right-hand operand. Why not use the toInt() method then? It also throws an exception when the input is not a number. You may decide to throw your own exception with a more descriptive message.
val numberWithCustomException = input?.toIntOrNull()
?: throw IllegalArgumentException("Invalid input, please enter a number")
println("Number of students with custom exception: $numberWithCustomException")
The IllegalArgumentException is a built-in exception in the Kotlin standard library. Use it if, as the name suggests, the argument turns out not to be what you expected.
Now, assume that the input is always a number. For instance, it’s checked before the invocation of your method. But, for some reason, like backwards compatibility, the signature of your method remains unchanged. It takes an array of strings as an argument. You’re sure that the first element of the array is a number. In such a case, you can use the not-null assertion operator.
val nonNullableNumber = input!!.toInt()
println("Non-nullable number of students: $nonNullableNumber")
The not-null assertion operator is a postfix operator. It’s used to assert that an expression is not null and to convert it to a non-nullable type. If it turns out to be null, then an exception is thrown. Use it with caution because it bypasses the null checks built in to the Kotlin language.
In that particular case, use the toInt() method instead of the toIntOrNull(). It returns a non-nullable type or throws an exception if the input is not a number.
Note: Avoid using the not-null assertion operator. It isn’t safe. It bypasses the null checks. Try to rewrite the code so that it doesn’t need to use that operator.
The next topic is the safe cast operator. It means that it doesn’t throw an exception when the cast is not possible. It returns null instead.
val safeCastNumber = nullableNumber as? Int
println("Safe cast number: $safeCastNumber")
Now, write a code that handles the platform types.
val studentsInClassrooms = Arrays.asList(10, 20, null, 25)
The variable studentsInClassrooms holds the number of students in each of the classrooms. There are 10 students in the first classroom and 20 in the second. The number of students in the third classroom is not known yet.
Arrays.asList() is a method from the Java standard library.
It builds a list from the given arguments. In the real projects, written from the beginning with Kotlin, you should use the analogous APIs from the Kotlin standard library to build such lists. They expose the nullability information. This code is used only for demonstration purposes.
The studentsInClassrooms list has a platform type. It’s a list of integers that are neither nullable nor non-nullable. Try to specify the nullable type explicitly and run the program.
val studentsInClassrooms: MutableList<Int?> = Arrays.asList(10, 20, null, 25)
Now, change the type to non-nullable Int and see what happens.
val studentsInClassrooms: MutableList<Int> = Arrays.asList(10, 20, null, 25)
The program still compiles and runs despite the fact that there’s a null value in the list, but the list is declared to hold only non-nullable integers. There’s no error yet because the Kotlin compiler doesn’t perform any null checks on the platform types. Try to extract the list elements.
val studentsInClassrooms: MutableList<Int> = Arrays.asList(10, 20, null, 25)
println("Number of students in the second classroom:")
println(studentsInClassrooms[1])
println("Number of students in the third classroom:")
println(studentsInClassrooms[2])
The last line causes an exception. It’s thrown when the studentsInClassrooms[2] is evaluated.
The Kotlin runtime expected a non-nullable integer, but it found a null value. Now, change the type to nullable Int? and run the program again.
val studentsInClassrooms: MutableList<Int?> = Arrays.asList(10, 20, null, 25)
No exception is thrown because all the types match. The studentsInClassrooms[2] is a nullable integer. Note the indices. The second element of the list is at index 1 and the third element is at index 2. That’s not a mistake. The indices are zero-based — they start from zero, not from one.
Finally, remove the type declaration and run the program again.
val studentsInClassrooms = Arrays.asList(10, 20, null, 25)
Note: This is important!
Be careful when using the Java APIs in your Kotlin projects. The Kotlin compiler doesn’t enforce any null checks on the platform types. It’s your responsibility to choose the correct nullability.