Spec-Zone.ru › Kotlin 1.8

Из Java в Kotlin: Руководство по обработке значений null

Обработка значений null — это возможность переменной содержать значение null. Когда переменная содержит null, попытка обратиться к ее значению приводит к NullPointerException. Существует множество способов написания кода, чтобы свести к минимуму вероятность возникновения исключений при обращении к null.

Это руководство о различиях в подходах Java и Kotlin к обработке потенциально нулевых переменных. Оно поможет вам перейти с Java на Kotlin и писать код в истинно котиновском стиле.

Первая часть руководства посвящена самому важному отличию — поддержке nullable типов в Kotlin и тому, как Kotlin обрабатывает типы из Java-кода. Вторая часть, начиная с проверки результата вызова функции, рассматривает несколько конкретных случаев, чтобы объяснить определенные различия.

Дополнительная информация об обработке значений null в Kotlin.

Поддержка nullable типов

Самое важное различие между системами типов Kotlin и Java — явная поддержка Kotlin nullable типов. Это способ указать, какие переменные могут содержать значение null. Если переменная может содержать null, вызов метода на ней небезопасен, так как это может привести к NullPointerException. Kotlin запрещает такие вызовы на этапе компиляции и тем самым предотвращает многие возможные исключения. Во время выполнения объекты nullable типов и объекты non-nullable типов обрабатываются одинаково: nullable тип не является оболочкой для non-nullable типа. Все проверки выполняются на этапе компиляции. Это означает, что для работы с nullable типами в Kotlin практически нет накладных расходов во время выполнения.

Мы говорим «практически», потому что, хотя внутренние проверки генерируются, их накладные расходы минимальны.

В Java, если вы не пишете проверки на null, методы могут выбросить NullPointerException.

// Java
int stringLength(String a) {
    return a.length();
}

void main() {
    stringLength(null); // Throws a `NullPointerException`
}

Этот вызов будет иметь следующий результат:

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)

В Kotlin все базовые типы по умолчанию non-nullable, если вы их не объявите как nullable. Если вы не ожидаете, что a будет null, объявите функцию stringLength() следующим образом:

// Kotlin
fun stringLength(a: String) = a.length

Параметр a имеет тип String, что в Kotlin означает, что он всегда должен содержать экземпляр String и не может содержать null. Nullable типы в Kotlin обозначаются знаком вопроса ?, например, String?. Ситуация с NullPointerException во время выполнения невозможна, если a является String, так как компилятор проверяет правило, что все аргументы функции stringLength() не должны быть null.

Попытка передать значение null функции stringLength(a: String) приведет к ошибке компиляции: «Null не может быть значением не-null типа String»:

Passing null to a non-nullable function error

Если вы хотите использовать эту функцию с любыми аргументами, включая null, используйте знак вопроса после типа аргумента String? и проверьте внутри тела функции, что значение аргумента не является null:

// Kotlin
fun stringLength(a: String?): Int = if (a != null) a.length else 0

После успешной проверки компилятор обрабатывает переменную как будто она имеет non-nullable тип String в области, где компилятор выполняет проверку.

Если вы не выполните эту проверку, код не будет скомпилирован с сообщением: «Допустимы только безопасные (?.) или не-нулевые (!) вызовы на nullable получателе типа String?».

Вы можете записать это короче — использовать оператор безопасного вызова ?. (Краткая запись для проверки на null), который позволяет объединить проверку на null и вызов метода в одной операции:

// Kotlin
fun stringLength(a: String?): Int = a?.length ?: 0

Типы платформы

В Java вы можете использовать аннотации, показывающие, может ли или не может переменная быть null. Такие аннотации не являются частью стандартной библиотеки, но вы можете добавить их отдельно. Например, вы можете использовать аннотации JetBrains @Nullable и @NotNull (из пакета org.jetbrains.annotations), или аннотации из Eclipse (org.eclipse.jdt.annotation). Kotlin распознает такие аннотации при вызове Java-кода из Kotlin-кода и будет обрабатывать типы в соответствии с их аннотациями.

Если ваш Java-код не содержит этих аннотаций, тогда Kotlin будет обрабатывать Java-типы как платформенные типы. Поскольку у Kotlin нет информации о nullable для таких типов, компилятор позволит все операции с ними. Вам нужно будет решить, выполнять ли проверки на null, потому что:

  • Так же, как и в Java, вы получите NullPointerException при попытке выполнить операцию на null.

  • Компилятор не будет выделять лишние проверки на null, что он обычно делает, когда вы выполняете безопасную операцию на значении non-nullable типа.

Дополнительная информация о вызове Java из Kotlin с точки зрения безопасности при работе с null и платформенными типами.

Проверка результата вызова функции

Одна из самых распространенных ситуаций, когда вам нужно проверять наличие null — это получение результата вызова функции.

В следующем примере есть два класса, Order и Customer. Order содержит ссылку на экземпляр Customer. Функция findOrder() возвращает экземпляр класса Order, или null если заказ не найден. Цель — обработать экземпляр клиента из полученного заказа.

Вот классы на Java:

//Java
record Order (Customer customer) {}

record Customer (String name) {}

В Java вызовите функцию и выполните проверку if-not-null на результате, чтобы продолжить обращение к нужному свойству:

// Java
Order order = findOrder();

if (order != null) {
    processCustomer(order.getCustomer());
}

Прямое преобразование Java-кода в Kotlin-код приводит к следующему:

// Kotlin
data class Order(val customer: Customer)

data class Customer(val name: String)

val order = findOrder()

// Direct conversion
if (order != null){
    processCustomer(order.customer)
}

Используйте оператор безопасного вызова ?. (Краткая запись для проверки на null) в сочетании с любыми из функций области видимости из стандартной библиотеки. Для этого обычно используется функция let:

// Kotlin
val order = findOrder()

order?.let {
    processCustomer(it.customer)
}

Вот более краткая версия того же:

// Kotlin
findOrder()?.customer?.let(::processCustomer)

Значения по умолчанию вместо null

Проверка на null часто используется в сочетании с установкой значения по умолчанию в случае успешной проверки на null.

Java-код с проверкой на null:

// Java
Order order = findOrder();
if (order == null) {
    order = new Order(new Customer("Antonio"))
}

Для того же выражения на Kotlin используйте оператор Elvis (Краткая запись для проверки на null):

// Kotlin
val order = findOrder() ?: Order(Customer("Antonio"))

Функции, возвращающие значение или null

В Java нужно быть внимательным при работе с элементами списка. Всегда проверяйте существование элемента по индексу перед использованием элемента:

// Java
var numbers = new ArrayList<Integer>();
numbers.add(1);
numbers.add(2);

System.out.println(numbers.get(0));
//numbers.get(5) // Exception!

Стандартная библиотека Kotlin часто предоставляет функции, названия которых указывают, могут ли они вернуть значение null. Это особенно характерно для API коллекций:

fun main() {
//sampleStart
    // Kotlin
    // The same code as in Java:
    val numbers = listOf(1, 2)
    
    println(numbers[0])  // Can throw IndexOutOfBoundsException if the collection is empty
    //numbers.get(5)     // Exception!

    // More abilities:
    println(numbers.firstOrNull())
    println(numbers.getOrNull(5)) // null
//sampleEnd
}

Агрегированные операции

Если нужно получить наибольший элемент или null в случае отсутствия элементов, в Java вы использовали бы API потоков:

// Java
var numbers = new ArrayList<Integer>();
var max = numbers.stream().max(Comparator.naturalOrder()).orElse(null);
System.out.println("Max: " + max);

В Kotlin используйте агрегированные операции:

// Kotlin
val numbers = listOf<Int>()
println("Max: ${numbers.maxOrNull()}")

Дополнительная информация о коллекциях в Java и Kotlin.

Безопасное приведение типов

При безопасном приведении типа в Java используется оператор instanceof и затем проверка результата:

// Java
int getStringLength(Object y) {
    return y instanceof String x ? x.length() : -1;
}

void main() {
    System.out.println(getStringLength(1)); // Prints `-1`
}

Чтобы избежать исключений в Kotlin, используйте оператор безопасного приведения as?, который возвращает null при неудаче:

// Kotlin
fun main() {
    println(getStringLength(1)) // Prints `-1`
}

fun getStringLength(y: Any): Int {
    val x: String? = y as? String // null
    return x?.length ?: -1 // Returns -1 because `x` is null
}

В примере на Java функция getStringLength() возвращает результат примитивного типа int. Чтобы функция возвращала null, можно использовать упакованный тип Integer. Однако, эффективнее сделать такие функции возвращающими отрицательное значение и затем проверить это значение — проверку всё равно придётся выполнять, но дополнительная упаковка не выполняется.

Что дальше?

  • Просмотрите другие Kotlin-идиомы.

  • Узнайте, как преобразовать существующий код Java в Kotlin с помощью конвертера Java в Kotlin (J2K).

  • Посмотрите другие руководства по миграции:

    • Строки в Java и Kotlin

    • Коллекции в Java и Kotlin.

Если у вас есть любимая идиома, не стесняйтесь поделиться ею с нами, отправив запрос на добавление!

Последнее изменение: 10 января 2023 г.
Коллекции в Java и Kotlin Начало работы с Kotlin/JS для React

© 2010–2023 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/java-to-kotlin-nullability-guide.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API