Ключевое слово Late и Null-безопасность
Часть раздела Объектно-ориентированное программирование путешествия по Dart на Coddy — урок 26 из 110.
Иногда вам нужна переменная, не допускающая значения null, но вы не можете присвоить ей значение сразу. Ключевое слово late говорит Dart: «Я обещаю инициализировать это перед использованием».
late String description;
void setup() {
description = 'Initialized later';
}
void main() {
setup();
print(description); // Работает — инициализировано перед использованием
}Без late Dart будет жаловаться, что description не инициализирована. С late вы берете на себя ответственность за то, чтобы она имела значение перед доступом. Если вы попытаетесь прочитать её до инициализации, вы получите ошибку времени выполнения.
Распространенным сценарием использования являются ресурсоемкие вычисления, выполнение которых вы хотите отложить до тех пор, пока они действительно не понадобятся:
class DataProcessor {
late List<int> data = _loadData();
List<int> _loadData() {
print('Loading data...');
return [1, 2, 3, 4, 5];
}
}
DataProcessor processor = DataProcessor();
// 'Loading data...' еще не было напечатано
print(processor.data); // Теперь данные загружаются и выводятсяМетод _loadData() выполняется только при первом обращении к data, а не при создании объекта. Такая ленивая инициализация может повысить производительность в случаях, когда значение может никогда не понадобиться.
Используйте late, когда вы уверены, что переменная будет инициализирована перед использованием, но не можете сделать это при объявлении. Это обещание компилятору — нарушьте его, и ваша программа завершится с ошибкой.
Задание
ЛегкоДавайте создадим менеджер конфигурации, который демонстрирует возможности переменных late для отложенной инициализации. Вы создадите систему, в которой ресурсоемкие операции настройки выполняются только тогда, когда конфигурация действительно необходима.
Вы организуете свой код в два файла:
config_manager.dart: Определите классConfigManager, который управляет настройками приложения. Класс должен иметь:- Поле
String appName, передаваемое через конструктор - Поле
late String databaseUrl, которое будет инициализировано позже через метод настройки - Поле
late int maxConnectionsс ленивой инициализацией, которое выводитCalculating max connections...при первом обращении и возвращает100 - Метод
setupDatabase(String url), который инициализируетdatabaseUrl - Метод
displayConfig(), который выводит текущую конфигурацию
- Поле
main.dart: Импортируйте ваш менеджер конфигурации и продемонстрируйте, как работают переменные late:- Создайте
ConfigManagerс именем приложения'MyApp' - Выведите
Config created - Вызовите
setupDatabaseс URL-адресом'localhost:5432/mydb' - Выведите
Accessing max connections... - Выведите значение
maxConnections - Выведите
Accessing max connections again... - Снова выведите значение
maxConnections(обратите внимание, что сообщение о расчете не повторяется) - Вызовите
displayConfig()
- Создайте
Метод displayConfig() должен выводить данные в следующем формате:
--- Configuration ---
App: [appName]
Database: [databaseUrl]
Max Connections: [maxConnections]Обратите внимание, что ленивый расчет maxConnections выполняется только один раз — при первом обращении к нему. В этом и заключается преимущество late с инициализатором!
Ожидаемый вывод:
Config created
Accessing max connections...
Calculating max connections...
100
Accessing max connections again...
100
--- Configuration ---
App: MyApp
Database: localhost:5432/mydb
Max Connections: 100Шпаргалка
Ключевое слово late позволяет объявлять переменные, не допускающие значения null, которые будут инициализированы позже, перед их использованием:
late String description;
void setup() {
description = 'Initialized later';
}
void main() {
setup();
print(description); // Работает — инициализировано перед использованием
}Без late Dart требует немедленной инициализации. С late вы обещаете инициализировать переменную перед обращением к ней. Чтение неинициализированной переменной late приводит к ошибке во время выполнения.
Ленивая инициализация: Когда у переменной late есть инициализатор, инициализация откладывается до первого обращения к переменной:
class DataProcessor {
late List<int> data = _loadData();
List<int> _loadData() {
print('Loading data...');
return [1, 2, 3, 4, 5];
}
}
DataProcessor processor = DataProcessor();
// 'Loading data...' еще не напечатано
print(processor.data); // Теперь она загружается и печатаетсяМетод _loadData() запускается только при первом обращении к data, а не при создании объекта. Это повышает производительность, когда значение может никогда не понадобиться или когда инициализация ресурсозатратна.
Попробуйте сами
import 'config_manager.dart';
void main() {
// TODO: Создайте ConfigManager с именем приложения 'MyApp'
// TODO: Выведите 'Config created'
// TODO: Вызовите setupDatabase с URL 'localhost:5432/mydb'
// TODO: Выведите 'Accessing max connections...'
// TODO: Выведите значение maxConnections
// TODO: Выведите 'Accessing max connections again...'
// TODO: Снова выведите значение maxConnections
// (обратите внимание, что сообщение о расчете не повторяется)
// TODO: Вызовите displayConfig()
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
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
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикПаттерн Репозиторий