Menu
Coddy logo textTech

Nullable и Non-Nullable типы

Часть раздела Объектно-ориентированное программирование путешествия по Dart на Coddy — урок 24 из 110.

Теперь, когда вы понимаете, почему важна безопасность к null (null safety), давайте посмотрим, как объявлять переменные, которые могут содержать null. В Dart вы делаете тип допускающим значение null (nullable), добавляя ? после имени типа.

String name = 'Alice';   // Non-nullable — всегда должен иметь значение
String? nickname;        // Nullable — может быть null или String

print(name);      // Alice
print(nickname);  // null

Ключевое отличие заключается в том, что типы, не допускающие значения null (non-nullable), гарантируют наличие значения, в то время как типы, допускающие значение null (nullable), явно допускают отсутствие значения. Это различие применимо ко всем типам:

int count = 10;       // Должно иметь значение
int? optionalCount;   // Может быть null

List<String> items = [];      // Должен быть список (может быть пустым)
List<String>? maybeItems;     // Может быть null (список отсутствует вовсе)

Обратите внимание, что пустой список [] отличается от null. Пустой список существует, но ничего не содержит; null означает, что списка вообще не существует.

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

String? maybeName = 'Bob';
// String definite = maybeName;  // Ошибка! Нельзя присвоить nullable-значение переменной, не допускающей null

В следующем уроке вы изучите операторы, которые предоставляет Dart для безопасной работы с nullable-значениями.

challenge icon

Задание

Легко

Давайте создадим систему списка просмотра фильмов, которая демонстрирует разницу между обязательной и необязательной информацией, используя nullable и non-nullable типы.

Вы создадите два файла для организации вашего кода:

  • movie.dart: Определите класс Movie, который представляет фильм в вашем списке. У каждого фильма должны быть название и год выпуска (они всегда обязательны), но некоторые дополнительные детали могут быть доступны не для каждого фильма. Ваш класс должен содержать:
    • Non-nullable String title — у каждого фильма должно быть название
    • Non-nullable int year — у каждого фильма должен быть год выпуска
    • Nullable String? director — режиссер может быть неизвестен
    • Nullable double? rating — не все фильмы еще получили оценку
    • Конструктор, который принимает title и year как обязательные параметры
    • Метод displayInfo(), который выводит информацию о фильме, отображая "Unknown" для отсутствующего режиссера и "Not rated" для отсутствующего рейтинга
  • main.dart: Импортируйте ваш класс фильма и создайте небольшой список просмотра, демонстрирующий как полные, так и неполные данные о фильмах:
    • Создайте фильм с названием 'Inception' и годом 2010, затем установите его режиссера в 'Christopher Nolan' и рейтинг в 8.8
    • Создайте фильм с названием 'The Matrix' и годом 1999, затем установите только его рейтинг в 8.7 (оставьте режиссера как null)
    • Создайте фильм с названием 'Upcoming Film' и годом 2025 (оставьте и режиссера, и рейтинг как null)
    • Вызовите displayInfo() для каждого фильма в порядке, указанном выше

Метод displayInfo() должен выводить данные именно в таком формате:

[title] ([year])
Director: [director or "Unknown"]
Rating: [rating or "Not rated"]

Обратите внимание, что для non-nullable полей (title и year) всегда гарантировано наличие значений, в то время как nullable поля (director? и rating?) могут оставаться не заданными. Это отражает реальные сценарии, где одни данные являются обязательными, а другие — необязательными.

Ожидаемый вывод:

Inception (2010)
Director: Christopher Nolan
Rating: 8.8
The Matrix (1999)
Director: Unknown
Rating: 8.7
Upcoming Film (2025)
Director: Unknown
Rating: Not rated

Шпаргалка

Чтобы сделать тип допускающим значение null (nullable) в Dart, добавьте ? после имени типа:

String name = 'Alice';   // Не допускает null - всегда должно быть значение
String? nickname;        // Допускает null - может быть null или String

Это различие применимо ко всем типам:

int count = 10;       // Должно иметь значение
int? optionalCount;   // Может быть null

List<String> items = [];      // Должен быть список (может быть пустым)
List<String>? maybeItems;     // Может быть null (списка нет вовсе)

Пустой список [] отличается от null. Пустой список существует, но ничего не содержит; null означает, что списка не существует вовсе.

Компилятор строго соблюдает правила null-безопасности. Вы не можете присвоить null переменной, не допускающей null, и вы не можете использовать переменную, допускающую null, там, где ожидается переменная, не допускающая null, без предварительной проверки на null:

String? maybeName = 'Bob';
// String definite = maybeName;  // Ошибка! Нельзя присвоить nullable-значение переменной, не допускающей null

Попробуйте сами

import 'movie.dart';

void main() {
  // TODO: Создайте фильм с названием 'Inception' и годом 2010
  // Затем установите его режиссера на 'Christopher Nolan' и рейтинг на 8.8
  
  // TODO: Создайте фильм с названием 'The Matrix' и годом 1999
  // Затем установите только его рейтинг на 8.7 (оставьте режиссера как null)
  
  // TODO: Создайте фильм с названием 'Upcoming Film' и годом 2025
  // Оставьте и режиссера, и рейтинг как null
  
  // TODO: Вызовите displayInfo() для каждого фильма по порядку
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование