В чем разница между dynamic, var и Object в Dart?

«В чем разница между dynamic, var и Object в Dart?» — вопрос из категории Dart Core, который задают на 59% собеседований Flutter Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Эти три ключевых слова служат разным целям в системе типов Dart, и их часто путают.

Ключевое слово Статический тип Изменение типа Проверка типов Основное назначение
var Выводится при инициализации Невозможно На этапе компиляции Укорочение записи для локальных переменных с очевидным типом.
dynamic dynamic (специальный тип) Возможно На этапе выполнения (отложена) Работа с динамическим контентом (JSON, плагины), когда тип неизвестен на этапе компиляции.
Object? Object? (базовый nullable-тип) Невозможно (но можно присвоить любой объект) На этапе компиляции Хранение любого объекта с обязательной проверкой или приведением типа перед использованием его специфичных методов.

Детальное сравнение с примерами:

  1. var — вывод типа:

    var message = 'Привет'; // Статический тип выведен как `String`
    // message = 123; // ОШИБКА компиляции: тип `int` не присваивается `String`.
    print(message.length); // OK, компилятор знает, что это String.
  2. dynamic — отключение статической проверки:

    dynamic item = 'Текст';
    print(item.length); // OK, работает (item — String).
    
    item = 100; // Теперь item содержит `int`.
    print(item.length); // ОШИБКА ВО ВРЕМЯ ВЫПОЛНЕНИЯ (Runtime Error)! 
    // Компилятор пропустит это, но при запуске у int нет свойства .length.

    dynamic следует использовать осознанно, только когда тип действительно неизвестен (например, данные из json.decode()).

  3. Object? — тип верхнего уровня:

    Object? something = 'Строка';
    something = 45; // OK, int — это тоже Object.
    something = null; // OK, Object? — nullable.
    
    // print(something.length); // ОШИБКА компиляции!
    // Компилятор не знает, что something — String, даже если там строка.
    
    // Требуется явная проверка или приведение типа:
    if (something is String) {
      print(something.length); // OK, в этой ветке тип продвинут до String.
    }
    // ИЛИ
    print((something as String).length); // Runtime error, если something не String.

Рекомендация по использованию:

  • Предпочитайте var для локальных переменных, где тип ясен.
  • Используйте явные типы (String, List<int>) для публичных API (полей классов, аргументов и возвращаемых значений методов) для лучшей документированности.
  • Object? используйте, когда вам нужна коллекция или переменная, гарантированно способная хранить любой объект, но с последующей безопасной проверкой типа (is).
  • dynamic — инструмент на крайний случай. Его использование должно быть ограничено сценариями, где статическая типизация невозможна.