Menu
Coddy logo textTech

Ключевое слово 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, когда вы уверены, что переменная будет инициализирована перед использованием, но не можете сделать это при объявлении. Это обещание компилятору — нарушьте его, и ваша программа завершится с ошибкой.

challenge icon

Задание

Легко

Давайте создадим менеджер конфигурации, который демонстрирует возможности переменных 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()
}
quiz iconПроверьте себя

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

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