- Тип null в Kotlin — Как правильно работать с типом null
- Основы использования Null в Kotlin
- Сторожевое значение (Sentinel) в Kotlin
- Основы использования nullable типов
- Nullability in Java and Kotlin
- Support for nullable types
- Platform types
- Checking the result of a function call
- Default values instead of null
- Functions returning a value or null
- Aggregate operations
- Casting types safely
- What’s next?
Тип null в Kotlin — Как правильно работать с типом null
У всех переменных и констант, с которыми мы работали до сих пор, были конкретные значения. У переменной типа string , вроде var name , есть строковое значение, которое с ней ассоциируется. К примеру, «Joe Howard» . Это может быть и пустая строка вроде «» , однако и в ней все же есть значение, к которому можно отсылаться.
Это одна из встроенных особенностей для безопасности в Kotlin. Если используется тип Int или String, тогда целое число или строка действительно существует — гарантированно.
В данном уроке будут рассмотрены nullable типы, которые позволяют представлять не только значение, но и его отсутствие. К концу урока вы узнаете, для чего нужны nullable типы и как их безопасно использовать.
Основы использования Null в Kotlin
Иногда полезно иметь возможность показать отсутствие значения. Представьте случай, когда вам нужно сделать ссылку на персональные данные человека: требуется сохранить его имя, возраст и род занятий. У всех людей есть имя и возраст, так что эти значения определенно будут присутствовать. Однако не у всех есть работа, поэтому важно, чтобы можно было не указывать род занятий.
Допустим, мы не знаем про null, тогда имя, возраст и род занятий человека можно было бы указать следующим образом:
Но что, если человек стал безработным? Может, он достиг просвещения и решил жить на вершине горы. Здесь пригодилась бы возможность указать на отсутствие значения.
Почему бы просто не использовать пустую строку? Это можно сделать, однако nullable типы будут наиболее оптимальным решением. Далее подробнее о том, почему именно.
Сторожевое значение (Sentinel) в Kotlin
Действительное значение, которое представляет специальное условие вроде отсутствия значения, называется сторожевое значением. В предыдущем примере это была бы пустая строка.
Давайте рассмотрим другой пример. Допустим, код запрашивает что-то с сервера, и вы используете переменную для хранения любого возвращаемого кода ошибки:
В случае успеха, отсутствие ошибки обозначается нулем. Это означает, что 0 является сторожевым (sentinel) значением. Как и пустая строка для названия рода деятельности, это работает, но может сбить с толку программиста. 0 на самом деле может быть допустимым кодом ошибки — или может быть в будущем, если сервер изменит свой ответ. В любом случае нельзя быть полностью уверенным в том, что сервер не вернул ошибку, не ознакомившись с документацией. В этих двух примерах было бы лучше, если бы существовал специальный тип, который мог бы представлять отсутствие значения . Тогда было бы понятно, когда значение существует, а когда нет.
Null является названием, которое дается отсутствию значения. Мы рассмотрим, как Kotlin напрямую внедряет данный концепт в язык довольно элегантным способом. Некоторые другие языки программирования просто используют сторожевые значения. В других есть концепт null значения, но это только синоним для нуля. Это просто другое sentinel значение.
Kotlin вводит целый новый набор типов — nullable типы. Они позволяют значению быть равным null . Если вы обрабатываете не-null тип, у вас точно будут какие-то значения, поэтому не нужно беспокоиться о существовании действительного значения. Аналогично, при использовании nullable типа вы должны обрабатывать случаи использования типа null . Так убирается двусмысленность, присущая использованию sentinel значениям.
Основы использования nullable типов
Nullable типы в Kotlin являются решением проблемы представления значения или его отсутствия. Nullable типы могут содержать какое-то значение или содержать null .
Концепт nullable можно сравнить с ящиком: в нем или есть значение, или его нет. Если значения нет, он содержит null . Ящик сам по себе существует всегда. Вы всегда можете его открыть и посмотреть, что внутри.
С другой стороны, у типов String или Int нет такого ящика. Вместо этого у них в любом случае будет какое-то значение, к примеру, «hello» или 42 . Помните, что у не-null типов гарантированно есть действительное значение.
На заметку: Многие из вас наверняка слышали о коте Шредингера. Идея nullable очень похожа на данный концепт, за тем исключением, что это не вопрос жизни и смерти!
Переменная nullable типа объявляется через использование следующего синтаксиса:
Nullability in Java and Kotlin
Nullability is the ability of a variable to hold a null value. When a variable contains null , an attempt to dereference the variable leads to a NullPointerException . There are many ways to write code in order to minimize the probability of receiving null pointer exceptions.
This guide covers differences between Java’s and Kotlin’s approaches to handling possibly nullable variables. It will help you migrate from Java to Kotlin and write your code in authentic Kotlin style.
The first part of this guide covers the most important difference – support for nullable types in Kotlin and how Kotlin processes types from Java code. The second part, starting from Checking the result of a function call, examines several specific cases to explain certain differences.
Support for nullable types
The most important difference between Kotlin’s and Java’s type systems is Kotlin’s explicit support for nullable types. It is a way to indicate which variables can possibly hold a null value. If a variable can be null , it’s not safe to call a method on the variable because this can cause a NullPointerException . Kotlin prohibits such calls at compile time and thereby prevents lots of possible exceptions. At runtime, objects of nullable types and objects of non-nullable types are treated the same: A nullable type isn’t a wrapper for a non-nullable type. All checks are performed at compile time. That means there’s almost no runtime overhead for working with nullable types in Kotlin.
We say «almost» because, even though intrinsic checks are generated, their overhead is minimal.
In Java, if you don’t write null checks, methods may throw a NullPointerException :
This call will have the following output:
java.lang.NullPointerException: Cannot invoke «String.length()» because «a» is null at test.java.Nullability.stringLength(Nullability.java:8) at test.java.Nullability.main(Nullability.java:12) at java.base/java.util.ArrayList.forEach(ArrayList.java:1511) at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
In Kotlin, all regular types are non-nullable by default unless you explicitly mark them as nullable. If you don’t expect a to be null , declare the stringLength() function as follows:
The parameter a has the String type, which in Kotlin means it must always contain a String instance and it cannot contain null . Nullable types in Kotlin are marked with a question mark ? , for example, String? . The situation with a NullPointerException at runtime is impossible if a is String because the compiler enforces the rule that all arguments of stringLength() not be null .
An attempt to pass a null value to the stringLength(a: String) function will result in a compile-time error, «Null can not be a value of a non-null type String»:
If you want to use this function with any arguments, including null , use a question mark after the argument type String? and check inside the function body to ensure that the value of the argument is not null :
After the check is passed successfully, the compiler treats the variable as if it were of the non-nullable type String in the scope where the compiler performs the check.
If you don’t perform this check, the code will fail to compile with the following message: «Only safe (?.) or non-null asserted (. ) calls are allowed on a nullable receiver of type String?».
You can write the same shorter – use the safe-call operator ?. (If-not-null shorthand), which allows you to combine a null check and a method call into a single operation:
Platform types
In Java, you can use annotations showing whether a variable can or cannot be null . Such annotations aren’t part of the standard library, but you can add them separately. For example, you can use the JetBrains annotations @Nullable and @NotNull (from the org.jetbrains.annotations package) or annotations from Eclipse ( org.eclipse.jdt.annotation ). Kotlin can recognize such annotations when you’re calling Java code from Kotlin code and will treat types according to their annotations.
If your Java code doesn’t have these annotations, then Kotlin will treat Java types as platform types. But since Kotlin doesn’t have nullability information for such types, its compiler will allow all operations on them. You will need to decide whether to perform null checks, because:
- Just as in Java, you’ll get a NullPointerException if you try to perform an operation on null .
- The compiler won’t highlight any redundant null checks, which it normally does when you perform a null-safe operation on a value of a non-nullable type.
Checking the result of a function call
One of the most common situations where you need to check for null is when you obtain a result from a function call.
In the following example, there are two classes, Order and Customer . Order has a reference to an instance of Customer . The findOrder() function returns an instance of the Order class, or null if it can’t find the order. The objective is to process the customer instance of the retrieved order.
Here are the classes in Java:
In Java, call the function and do an if-not-null check on the result to proceed with the dereferencing of the required property:
Converting the Java code above to Kotlin code directly results in the following:
// Kotlin data class Order(val customer: Customer) data class Customer(val name: String) val order = findOrder() // Direct conversion if (order != null) < processCustomer(order.customer) >
Use the safe-call operator ?. (If-not-null shorthand) in combination with any of the scope functions from the standard library. The let function is usually used for this:
Here is a shorter version of the same:
Default values instead of null
Checking for null is often used in combination with setting the default value in case the null check is successful.
The Java code with a null check:
Functions returning a value or null
In Java, you need to be careful when working with list elements. You should always check whether an element exists at an index before you attempt to use the element:
// Java var numbers = new ArrayList
The Kotlin standard library often provides functions whose names indicate whether they can possibly return a null value. This is especially common in the collections API:
Aggregate operations
When you need to get the biggest element or null if there are no elements, in Java you would use the Stream API:
// Java var numbers = new ArrayList
Casting types safely
When you need to safely cast a type, in Java you would use the instanceof operator and then check how well it worked:
To avoid exceptions in Kotlin, use the safe cast operator as? , which returns null on failure:
In the Java example above, the function getStringLength() returns a result of the primitive type int . To make it return null , you can use the boxed type Integer . However, it’s more resource-efficient to make such functions return a negative value and then check the value – you would do the check anyway, but no additional boxing is performed this way.
What’s next?
- Browse other Kotlin idioms.
- Learn how to convert existing Java code to Kotlin with the Java-to-Kotlin (J2K) converter.
- Check out other migration guides:
- Strings in Java and Kotlin
- Collections in Java and Kotlin
If you have a favorite idiom, feel free to share it with us by sending a pull request!