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-значениями.
Задание
ЛегкоДавайте создадим систему списка просмотра фильмов, которая демонстрирует разницу между обязательной и необязательной информацией, используя 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"для отсутствующего рейтинга
- Non-nullable
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() для каждого фильма по порядку
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыБиблиотеки и импортВведение в ООПКлассы и объектыКлючевое слово thisМетодыПеременные экземпляраОсновы конструкторовИтоги — Простой калькулятор4Null-безопасность
Введение в Null-безопасностьNullable и Non-Nullable типыОператоры ? и !Ключевое слово Late и Null-безопасностьNull-aware операторыNull-безопасность в классахИтоги — Система профилей пользователей7Абстрактные классы и интерфейсы
Абстрактные классыАбстрактные методыИнтерфейсы в DartНеявные интерфейсыРеализация против наследованияМножественные интерфейсыИтоги — Калькулятор фигур10Коллекции и обобщения
Обзор List, Set, MapТипобезопасные коллекцииОбобщенные классыОбобщенные методыОграничения обобщенийIterable и IteratorИтоги: Обобщенное хранилище13Продвинутые концепции ООП
Композиция против наследованияМетоды расширенияВызываемые классыЗапечатанные классы (Dart 3)Записи (Dart 3)Паттерны и сопоставление (3.0)Перечисления с методами16Проект: Управление библиотекой
Обзор проектаКлассы Book и User2Конструкторы в Dart
Конструктор по умолчаниюИменованные конструкторыСписки инициализацииКонстантные конструкторыФабричные конструкторыПеренаправляющие конструкторыИтоги — Shape Builder5Инкапсуляция
Публичные и приватные членыСоглашение о префиксе _Приватность на уровне библиотекГеттеры и сеттеры: подробный разборСкрытие данныхИтоги — Записи о студентах8Mixins
Введение в MixinsСоздание MixinsИспользование нескольких MixinsКлючевое слово on в MixinsMixin и наследованиеMixin и интерфейсИтоги: Система животных11Специальные методы
Переопределение toString()Переопределение hashCode и ==Интерфейс ComparableМетод call()Переопределение noSuchMethodПовторение — Пользовательская коллекция14Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн ObserverПаттерн Strategy3Свойства классов
Члены экземпляра и статические членыПоля Final и ConstПеременные LateСтатические методы и поляГеттеры и сеттерыИтоги — Менеджер банковского счета6Наследование
Основы наследованияКлючевое слово superПереопределение методовАннотация @overrideКлючевое слово final для классовКонструкторы и наследованиеИтоги: Иерархия сотрудников9Полиморфизм
Основы полиморфизмаПолиморфизм через интерфейсыПроверка типов во время выполненияОператоры is и asКлючевое слово covariantИтоги — Обработчик платежей12Асинхронное ООП
Futures и async/awaitОсновы StreamsКонтроллеры StreamАсинхронные конструкторыАсинхронность в методах классовИтоги — Data Fetcher15Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикПаттерн Репозиторий