Dark Theme - On/Off
Pulsate - On
Waves - On

Типизация в Dart

Типизация — то как язык работает с данными разного типа (числами, строками, объектами).

Dart — язык со статической типизацией.

Статическая: Типы проверяются до запуска (на этапе компиляции). Если в коде ошибка типов — она вылезет сразу в IDE, и приложение не соберется.

Память и Изоляты (Isolates)

Так как Dart — это однопоточный язык, работа с памятью устроена вокруг Isolates. У каждого изолята своя выделенная память: своя куча (Heap), свой стек и свой сборщик мусора (GC). Они не делят память между собой.

  • Stack (Стек): Быстрая память. Там лежат только примитивы и указатели (адреса), которые живут, пока работает функция. Выполнилась функция — стек очистился.
  • Heap (Куча): Там живут сами объекты (тела виджетов, списки, данные). Куча большая, и её чистит GC.
  • Isolates: Это "коробочки". В каждой коробочке свой Стек, своя Куча и свой GC.

Garbage Collector (GC)

Сборщик мусора работает внутри изолятов и делится на две фазы, чтобы не тормозить UI:

1. Young Space (Молодое поколение):
➤ Туда попадают все новые объекты (например, виджеты).
➤ Память маленькая и очищается очень быстро, не вызывая фризов анимации.

2. Old Space (Старое поколение):
➤ Сюда попадают объекты, пережившие несколько очисток в Young Space (синглтоны, стейты).
➤ Очищается редко, так как проверка старого поколения работает медленнее.

Структуры данных (Collections)

1. Массив (List) vs Связанный список (LinkedList)
Разница в том, как элементы лежат в оперативной памяти.

List (Массив): Элементы лежат строго друг за другом.
Чтение: Моментально O(1). Мы знаем адрес первого элемента и индекс нужного.
Вставка/Удаление: Медленно O(n). Если удалить первый элемент, придется сдвигать все остальные в памяти.
Пример: Обычная лента новостей, где важен порядок и быстрый доступ.

Linked List (Связный список): Элементы разбросаны, каждый хранит ссылку на соседа.
Чтение: Медленно O(n). Чтобы найти 10-й элемент, нужно пройти по цепочке с 1-го по 9-й.
Вставка/Удаление: Очень быстро O(1). Нужно просто перекинуть ссылки у соседних элементов.
Пример: Очередь скачивания файлов или музыкальный плейлист.


2. List vs Set (Список против Множества)
Выбор зависит от того, важна ли уникальность данных и скорость поиска.

List: Упорядоченная коллекция. Элементы могут повторяться.
Set: Коллекция уникальных элементов. Порядок обычно не гарантирован.

В чем подвох на собесе? Скорость поиска .contains().
В List поиск идет перебором — O(n).
В Set поиск идет через хэш-таблицу — O(1) (моментально).
Пример: Хранить список ID "лайкнутых" постов лучше в Set, чтобы мгновенно проверять, нажал ли юзер на сердечко.


3. Map (Словарь / Хэш-таблица)
Хранит данные парами Ключ — Значение.

Как работает: Ключ прогоняется через хэш-функцию, которая превращает его в число — адрес в памяти. Это позволяет находить значение по ключу моментально за O(1).
Суть: Идеально для кэширования и работы с JSON.
Пример: Кэш пользователей, где ключ — userId, а значение — объект User.


4. Set (Множество) — HashSet vs LinkedHashSet
В Set все элементы уникальны. Поиск в нем всегда O(1).

HashSet: Самый быстрый. Не гарантирует никакой порядок элементов.
LinkedHashSet: (Дефолтный в Dart). Работает почти так же быстро, но сохраняет порядок вставки элементов.


5. Для чего нужно ключевое слово typedef?
typedef — это псевдоним для типа.

Чаще всего его используют для:
➤ сокращения длинных сигнатур функций;
➤ улучшения читаемости кода;
➤ явного описания, какую функцию мы ожидаем.

Простыми словами: typedef нужен, чтобы дать имя сложному типу, чаще всего функции. Это делает код чище и понятнее.


Равенство: operator == и hashCode
Принцип сравнения обьектов в Set / Как Dart понимает, что два разных объекта в памяти — это "один и тот же" элемент? Сравнивается по hashCode и equal

operator == (Equal)
По умолчанию Dart сравнивает ссылки (адреса в памяти). Два разных объекта с одинаковыми данными будут не равны. Чтобы сравнивать их по значениям полей, нужно переопределять оператор ==.

hashCode (можешь повторяться)
Это числовой идентификатор обьекта. Состояние может быть одинаковым у обьектов, а адрес разный.
Зачем он нужен: Когда ты кладешь объект в Set или Map, Dart не сравнивает его со всеми остальными через == (это долго). Он сначала смотрит на hashCode, чтобы мгновенно найти нужную "корзину" в памяти.

Зачем переопределять оба? Почему переопределяется и hashCode, и == (equal)?
Потому что Map и Set сначала ищут "полку" по хэш-коду, а потом проверяют конкретный объект через ==. Если переопределить только одно, объект просто не найдется

Они должны быть согласованы.
Если два объекта равны через ==, то у них обязан быть одинаковый hashCode.
Это нужно для правильной работы Set и Map



Шпаргалка по сложности (Big O):
  • Поиск в List: O(n)
  • Поиск в Set / Map: O(1)
  • Доступ по индексу в List: O(1)
Вердикт: Если нужно часто проверять наличие элемента в огромном списке — используй Set. Если нужно хранить пары данных — Map. Если важен порядок и обращение по индексу — List.

Переменные и Константы (var, final, const)

Различаются по времени инициализации: Compile-time (до запуска) или Runtime (во время работы приложения).


1. var (Неявная типизация)
Это: ключевое слово и используется для создания переменных. Она значит изменяемая - variable. В процессе работы ей можно присвоить новое значение в рамках ТИПА!
Суть: Dart (анализатор) сам определяет тип по первому значению (когда уже инициализировали переменную).
Особенность: Значение менять можно, а вот тип — нельзя.


var name = 'Dart';
name = 'Flutter'; // Ок, значение поменяли
// name = 123; // Ошибка! Тип String уже зафиксирован
                        

Чем отличается var от dynamic?
Частый вопрос на собеседовании, так как оба позволяют не писать название типа явно.

var: Это статика. Тип определяется один раз при создании. После этого переменная ведет себя так, будто ты написал String или int.
dynamic: Это динамика. Ты буквально говоришь Dart: "Отключи проверку типов для этой переменной". Её тип может меняться прямо в процессе работы программы.


var a = 'Hello';
// a = 10; // ОШИБКА: String нельзя заменить на int

dynamic b = 'Hello';
b = 10; // ОК: динамика позволяет менять тип на лету
                        
Суть простыми словами:
Используй var для обычных переменных (это безопасно).
Используй dynamic только в крайних случаях (например, при парсинге JSON), когда ты реально не знаешь, что прилетит от сервера — число или строка.

2. final (Неизменяемая в Runtime)
Суть: Значение присваивается только один раз.
Сам обьект заменить мы НЕ можем, а вот положить что-то внутрь мы можем
Когда использовать: Когда мы не знаем значение заранее (например, результат из API или текущее время).


final time = DateTime.now(); // Ок, время узнаем только при запуске
final response = await api.getData(); // Ок, ждем данные из сети
// time = DateTime.now(); // Ошибка! Перезаписывать нельзя
                        

3. const (Константа компиляции)
Суть: Значение должно быть известно до запуска приложения (жесткая константа).
Оптимизация: Экономит память. Если создать 100 одинаковых const виджетов, Dart создаст в памяти всего один объект.


const pi = 3.14; // Ок, значение известно при написании кода
const mainFont = 'Metrophobic';
// const t = DateTime.now(); // Ошибка! До запуска мы не знаем время
                        
Главная разница (Блиц):
final — инициализируется в работе (runtime), можно менять содержимое (например, добавить элемент в список).
const — инициализируется при сборке (compile-time), делает "глубокую заморозку" (нельзя менять даже элементы списка).
Во Flutter: Всегда пиши const перед виджетами, которые не меняются, чтобы Flutter не тратил силы на их перерисовку.

Асинхронность в Dart

В программировании есть такие понятия как АСИНХРОННОСТЬ и МНОГОПОТОЧНОСТЬ — они близки друг к другу, но имеют принципиальное различие:

Многопоточность: 2 и более потока идут параллельно в одно и то же время.
Асинхронность: Она имитирует многопоточность, хотя по факту выполняется одна и та же функция, то есть поток всего ОДИН.

И так как в Dart поток всего один, нам нужны инструменты, чтобы управлять этим процессом «ожидания», не блокируя интерфейс. Для этого в языке есть три главных компонента: Future, async и await.


1. Future ("Коробка с обещанием")
Суть: Объект, который представляет результат асинхронной операции. Сначала он пуст, а через время в него упадет либо результат, либо ошибка.


Future<String> fetchUser() async {
  // Имитация задержки сети
  await Future.delayed(Duration(seconds: 2));
  return "User: Ivan";
}
                        

2. Ключевые слова async / await
Чтобы работать с этой коробкой внутри функции, мы помечаем саму функцию словом async. Это сигнал для компилятора, что внутри будет происходить асинхронная магия и функция обязательно вернет Future

async: — Модификатор функции. Ставится в сигнатуре метода (перед фигурными скобками). Он говорит компилятору: "Внутри этой функции будет ожидание, поэтому функция обязана возвращать Future".

И самое важное — слово await. Это точка паузы. Когда поток доходит до await, он говорит этой конкретной функции: "Стой и жди, пока коробка (Future) наполнится". Но сам поток не замирает — он выпрыгивает из этой функции и бежит дальше рисовать UI или обрабатывать клики. Как только данные в Future упали, поток возвращается и продолжает выполнять код после await
await: Точка паузы. Пишется перед вызовом другого асинхронного метода. Заставляет код внутри этой функции остановиться и ждать результата, не блокируя при этом всё остальное приложение.

А как поток понимает, когда пора вернуться в функцию после await?
Это происходит благодаря Event Loop (Циклу событий). Когда асинхронная операция завершается, она кидает событие в очередь. Event Loop на очередном круге видит: "О, данные для того await пришли!", берет их и возвращает поток в ту функцию, которую мы поставили на паузу


3. Stream (Поток данных)
Суть: Если Future возвращает значение один раз, то Stream — это труба, по которой данные могут лететь постоянно (например, сообщения в чате или изменение цен на бирже).

Почему это важно?
Если забыть про асинхронность и сделать тяжелый запрос в сеть "синхронно" — весь интерфейс Flutter (60 FPS) замрет. Пользователь увидит "зависшее" приложение, потому что единственный поток занят ожиданием ответа от сервера.
Блиц-итог:
Асинхронность: Один рабочий, который ставит чайник и идет резать хлеб (поток свободен).
Параллелизм (Isolates): Два рабочих, один варит кофе, другой моет пол (два потока/ядра).

Event Loop (Цикл событий)

Если Dart однопоточный, как он понимает, к чему возвращаться? Вся асинхронность в Dart держится на Event Loop (Цикл событий).
Простыми словами:Event Loop — это бесконечный цикл (карусель), который крутится внутри твоего приложения и проверяет очереди задач.

1. Microtask Queue: Срочные внутренние задачи (выполняются первыми до победного).
2. Event Queue: Клики, отрисовка UI, ответы от сервера (Future).

Как он работает:
1. Event Loop сначала проверяет, есть ли что-то в Microtask. Если есть — выполняет всё, пока очередь не опустеет.
2. Потом берет одно событие из Event Queue и выполняет его.
3. Снова бежит проверять Microtask. И так по кругу.

Mixins & Extensions

1. Mixins (Миксины) — Ключевое слово with
Зачем: В Dart нельзя наследоваться от двух классов одновременно (нет множественного наследования). Миксины позволяют "подмешать" готовую логику в любой класс.
Суть: Это не семья (наследование), это способность или "плагин" с кодом.


mixin Swimmable {
  void swim() => print("Я умею плавать!");
}

class Animal {}

// Подмешиваем способность через with
class Dolphin extends Animal with Swimmable {}

void main() {
  Dolphin().swim(); // Работает!
}
                        
Простыми словами: Наследование (extends) — это то, кто ты есть (Дельфин — это Животное). Миксин (with) — это то, что ты умеешь (Дельфин умеет плавать). Можно подмешивать сколько угодно способностей через запятую.

1.1 Может ли mixin наследоваться от другого класса?
Обычный mixin не может использовать extends как класс.
Но mixin может ограничивать, на какой класс его можно применять, через on.


1.2 Может ли mixin иметь конструктор?
Нет, обычный mixin не может иметь конструктор.
Потому что mixin — это не полноценный класс для создания объекта, а кусок поведения, который подмешивается в другой класс.


2. Extensions (Расширения) — Ключевое слово extension on
Зачем: Чтобы добавить новые методы в классы, которые ты не можешь менять (например, системные String, int, List).
Суть: Это "тюнинг". Мы не меняем оригинал, но добавляем ему новые "фишки", чтобы код был чище.


extension MyString on String {
  bool get isValidEmail => this.contains('@');
}

void main() {
  String email = "test@mail.com";
  print(email.isValidEmail); // true. Мы сами добавили этот метод в String!
}
                        
Простыми словами: Расширение — это как "прокачка" существующего типа. Теперь любая строка в твоем приложении умеет проверять саму себя на валидность почты, и тебе не нужно создавать для этого отдельные утилиты.

Interfaces & Abstract

1. Есть ли в Dart интерфейсы?
Да. Любой класс можно использовать как интерфейс через implements.


class Dog implements Animal {}
            

2. Можно ли имплементировать обычный класс и несколько классов?
Да. Можно обычный класс и сразу несколько через запятую.


class C implements A, B {}
            

3. Что такое abstract class?
Это класс-заготовка для наследования. Создать его объект напрямую нельзя.


abstract class Animal {}
            

4. Abstract и late — в чём разница?
abstract — реализации нет, наследник обязан сделать.
late — значение будет присвоено позже.


late String name;
            

5. Abstract class и mixin — отличие
abstract class — база через extends.
mixin — поведение через with.


class Dog extends Animal with CanRun {}
            
Коротко: abstract class = "кто ты", mixin = "что ты умеешь".

Error Handling (Обработка ошибок)

В Dart все проблемы делятся на две категории: те, которые мы можем исправить на ходу, и те, из-за которых программа должна немедленно остановиться.


1. Exception (Исключение) — "Проблема, которую можно решить"
Что это: Ситуация, которую мы предвидим. Например: пропал интернет, сервер не отвечает или файл не найден.
Как работаем: Оборачиваем опасный код в try-catch. Если что-то пойдет не так, приложение не вылетит, а просто выполнит запасной план (покажет ошибку юзеру).


try {
  var data = await api.fetchData(); // Пробуем загрузить
} on SocketException {
  print("Нет интернета!"); // Обрабатываем конкретную проблему
} catch (e) {
  print("Произошло что-то странное: $e");
}
                        

2. Error (Ошибка) — "Косяк программиста"
Что это: Критическая проблема в логике кода. Например: обращение к элементу списка по индексу, которого нет (out of bounds), или переполнение памяти.
Как работаем: Ошибки не нужно ловить через try-catch. Их нужно находить и исправлять в коде раз и навсегда.

Простая аналогия:
Exception: Это проколотое колесо. Ты ожидал, что это может случиться, у тебя есть запаска (catch), и ты едешь дальше.
Error: Это когда ты забыл залить масло и двигатель взорвался. Это твоя вина как владельца, и "починить" это на ходу невозможно — нужно было следить за машиной (кодом).

3. Finally — "Уборка в любом случае"
Суть: Этот блок кода выполнится всегда. Успешно ли прошел запрос или всё упало с ошибкой — finally отработает в конце.
Для чего: Идеально для закрытия файлов, отключения от базы данных или выключения индикатора загрузки (лоадера).


try {
  showLoading(); // Включили крутилку
  await api.load();
} finally {
  hideLoading(); // Выключили крутилку В ЛЮБОМ СЛУЧАЕ
}
                        
Резюме для собеса: Exception — это внешние обстоятельства, которые мы обрабатываем. Error — это наши ошибки в коде, которые мы фиксим. Finally — это гарантированная очистка ресурсов.

Null Safety (Безопасность)

Null Safety - это страховка того что приложение не упадет , это такое себе условие, а может ли быть переменная пустой (null) или нет.

В Dart все переменные по умолчанию не могут быть null (non-nullable). Если ты создал String name, ты обязан положить туда строку, иначе код не скомпилируется.


1. Операторы ? и !
Символ ? (Nullable): Говорит Dart, что переменная может содержать null.
Символ ! (Bang operator): Твоя клятва компилятору: "Я уверен, что тут сейчас не null, мамой клянусь!". Если там окажется null — приложение упадет.


String name = 'Ivan'; // Не может быть null
String? bio; // Может быть null

// print(bio.length); // Ошибка! Dart не даст вызвать метод у того, кто может быть null
print(bio!.length); // ОК, но если bio == null -> КРАШ
                        

2. Операторы ?? и ?. (Безопасность)
Оператор ??: Позволяет задать "запасное" значение, если слева оказался null.
Оператор ?.: Вызывает метод только если объект существует (не null).


String userBio = bio ?? 'Информация отсутствует'; // Защита
print(bio?.length); // Если bio null, выведет null, но не упадет
                        

3. Ключевое слово late
Суть: Мы обещаем Dart, что переменная точно будет инициализирована позже, но до того, как мы к ней обратимся.
Для чего: Обычно используется в initState(), когда значение переменной зависит от контекста, который недоступен в момент создания класса.

Суть простыми словами: Null Safety — это страховка. Компилятор заставляет тебя еще на этапе написания кода продумать, может ли значение быть пустым, и как ты это обработаешь. Это убирает 90% причин крашей приложения.

Специфичные типы: Object? и Never

В Dart есть два крайних типа: Object? (самый верхний) и Never (самый нижний).


1. Object? (Верхушка иерархии)
Что это: Базовый класс для абсолютно всех типов. В него можно положить что угодно: число, строку или null.
Суть: В отличие от dynamic, тип Object? — строгий. Ты не можешь вызвать метод у переменной, пока не проверишь её тип.


Object? data = "Привет";

// data.toUpperCase(); // ОШИБКА: Сначала нужно проверить тип!

if (data is String) {
  print(data.toUpperCase()); // ТЕПЕРЬ МОЖНО
}
                        

2. Never (Тип-тупик)
Что это: Тип, который не имеет значений. Используется для функций, которые никогда не возвращают результат.
Суть: Функция с типом Never либо всегда выкидывает ошибку (throw), либо содержит бесконечный цикл.


Never crashApp(String msg) {
  throw Exception(msg); // Функция "взрывается", код дальше не идет
}

void checkAuth(String? token) {
  if (token == null) crashApp("Нет токена");
  print(token.length); // Dart уверен, что тут token НЕ null, благодаря Never выше
}
                        
Коротко для запоминания:
Object? — это "Коробка для всего". Позволяет хранить любые данные, но заставляет проверять их тип перед использованием.
Never — это "Мертвая петля". Показывает компилятору, что выполнение программы на этом месте прервется (удобно для анализа потока данных).