Spec-Zone.ru › Kotlin 2

Типы, допускающие значение null, в Java и Kotlin

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

В этом руководстве рассматриваются различия между подходами Java и Kotlin к обработке переменных, которые могут иметь значение null. Оно поможет вам перейти с Java на Kotlin и писать код в аутентичном стиле Kotlin.

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

Подробнее о безопасности при работе с null в Kotlin.

Поддержка типов, допускающих значение null

Самое важное различие между системами типов Kotlin и Java — это явная поддержка в Kotlin типов, допускающих значение null. Это способ указать, какие переменные могут хранить значение null. Если переменная может быть null, вызывать её метод небезопасно, поскольку это может привести к NullPointerException. Kotlin запрещает такие вызовы во время компиляции и тем самым предотвращает множество возможных исключений. Во время выполнения объекты типов, допускающих значение null, и объекты типов, не допускающих его, обрабатываются одинаково: тип, допускающий значение null, не является обёрткой для типа, не допускающего значение null. Все проверки выполняются во время компиляции. Это означает, что работа с типами, допускающими значение null, в 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 все обычные типы по умолчанию не допускают значение null, если вы явно не укажете обратное. Если вы не ожидаете, что a будет null, объявите функцию stringLength() следующим образом:

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

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

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

Passing null to a non-nullable function error

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

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

После успешного прохождения проверки компилятор считает переменную типом, не допускающим значение null, String в области видимости, где выполняется проверка.

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

Ту же операцию можно записать короче: используйте оператор безопасного вызова ?. (сокращённая запись «если не null»), который позволяет объединить проверку на null и вызов метода в одну операцию:

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

Платформенные типы

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

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

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

  • Компилятор не будет отмечать избыточные проверки на null, хотя обычно он делает это, когда вы выполняете операцию с безопасной обработкой null для значения типа, не допускающего null.

Подробнее о вызове Java из Kotlin с точки зрения безопасности при работе с null и платформенных типов.

Поддержка типов, определённо не допускающих значение null

В Kotlin, если вы хотите переопределить метод Java, принимающий @NotNull в качестве аргумента, вам понадобятся типы Kotlin, определённо не допускающие значение null.

Например, рассмотрим следующий метод load() в Java:

import org.jetbrains.annotations.*;

public interface Game<T> {
  public T save(T x) {}
  @NotNull
  public T load(@NotNull T x) {}
}

Чтобы успешно переопределить метод load() в Kotlin, необходимо объявить T1 как тип, определённо не допускающий значение null (T1 & Any):

interface ArcadeGame<T1> : Game<T1> {
  override fun save(x: T1): T1
  // T1 is definitely non-nullable
  override fun load(x: T1 & Any): T1 & Any
}

Подробнее о типах с параметрами, определённо не допускающих значение null.

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

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

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

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

//Java
record Order (Customer customer) {}

record Customer (String name) {}

В Java вызовите функцию и проверьте результат условием «если не 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, используйте оператор Элвиса (сокращённую запись «если не 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 для этого используется Stream 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. Однако эффективнее использовать ресурсы, если такие функции возвращают отрицательное значение, после чего это значение проверяется: проверку всё равно пришлось бы выполнять, но в этом случае дополнительной упаковки не происходит.

При переносе кода Java в Kotlin сначала можно использовать обычный оператор приведения типа as с типом, допускающим значение null, чтобы сохранить исходную семантику кода. Однако мы рекомендуем адаптировать код и использовать оператор безопасного приведения типа as? — это более безопасный и идиоматичный подход. Например, если у вас есть следующий код на Java:

public class UserProfile {
    Object data;

    public static String getUsername(UserProfile profile) {
        if (profile == null) {
            return null;
        }
        return (String) profile.data;
    }
}

При непосредственном переносе с оператором as получится следующее:

class UserProfile(var data: Any? = null)

fun getUsername(profile: UserProfile?): String? {
    if (profile == null) {
        return null
    }
    return profile.data as String?
}

Здесь profile.data приводится к строке, допускающей значение null, с помощью as String?.

Рекомендуем пойти дальше и использовать as? String для безопасного приведения значения. При неудаче этот подход возвращает null вместо того, чтобы выбрасывать ClassCastException:

class UserProfile(var data: Any? = null)

fun getUsername(profile: UserProfile?): String? =
  profile?.data as? String

В этой версии выражение if заменено на оператор безопасного вызова ?., который безопасно получает доступ к свойству data перед попыткой приведения типа.

Что дальше?

  • Ознакомьтесь с другими идиомами Kotlin.

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

  • Ознакомьтесь с другими руководствами по переносу кода:

    • Строки в Java и Kotlin

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

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

10 ноября 2025 г.
Коллекции в Java и KotlinСтандартный ввод

© 2010–2026 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