В чём разница между List, Set и Map в Dart?
List — упорядоченная последовательность с дубликатами и доступом по индексу; Set — уникальные элементы с быстрым contains; Map — пары ключ/значение с уникальными ключами и O(1) доступом.
List, Set и Map в Dart
Dart предоставляет три основные коллекции из core-библиотеки, каждая со своей семантикой хранения и доступа.
List — упорядоченная последовательность
List<T> хранит элементы в порядке добавления, допускает дубликаты и обеспечивает доступ по индексу за O(1). Это ближайший аналог массива или ArrayList.
final List<String> langs = ['Dart', 'Kotlin', 'Swift', 'Dart'];
print(langs[0]); // Dart
print(langs.length); // 4 (дубликат сохранён)
langs.add('Go');
langs.remove('Kotlin');
langs.sort();
print(langs); // [Dart, Dart, Go, Swift]
Два варианта создания: List.filled(n, value) — фиксированного размера, [] — growable (по умолчанию).
Set — уникальные элементы без гарантии порядка
Set<T> гарантирует уникальность через хэш (реализация по умолчанию — LinkedHashSet, сохраняет порядок вставки). Проверка наличия элемента — O(1).
final Set<String> roles = {'admin', 'user', 'admin'};
print(roles); // {admin, user} — дубликат удалён
print(roles.contains('admin')); // true
final a = {1, 2, 3};
final b = {2, 3, 4};
print(a.intersection(b)); // {2, 3}
print(a.union(b)); // {1, 2, 3, 4}
print(a.difference(b)); // {1}
Map — пары ключ-значение
Map<K, V> — ассоциативный массив. Ключи уникальны; по умолчанию используется LinkedHashMap, сохраняющий порядок вставки. Доступ за O(1).
final Map<String, int> scores = {'Alice': 95, 'Bob': 82};
scores['Carol'] = 91;
print(scores['Alice']); // 95
print(scores['Unknown']); // null
print(scores.containsKey('Bob')); // true
// Итерация
scores.forEach((name, score) {
print('$name: $score');
});
// Безопасное обновление
scores.update('Bob', (v) => v + 5, ifAbsent: () => 0);
Сравнение по ключевым характеристикам
- List: порядок гарантирован, дубликаты разрешены, доступ по индексу.
- Set: только уникальные элементы, быстрая проверка
contains, операции над множествами. - Map: поиск по ключу, уникальные ключи, значения могут повторяться.
const-коллекции
Все три типа поддерживают const — создание неизменяемых коллекций на этапе компиляции:
const list = [1, 2, 3];
const set = {1, 2, 3};
const map = {'a': 1};
Подводные камни
- Пустой
{}— это Map, а не Set. Dart выводит типMap<dynamic, dynamic>, а неSet. Для пустого Set нужно явное<String>{}илиSet<String>(). - LinkedHashMap vs HashMap. Если порядок не важен и нужна максимальная скорость, явно используйте
HashMapизdart:collection. - List фиксированного размера.
List.filled(5, 0)бросаетUnsupportedErrorнаadd(). Передайтеgrowable: trueесли нужен рост. - Мутация во время итерации. Изменение List/Set внутри
forEachили циклаfor-inприводит кConcurrentModificationError. - Equals/hashCode для Set и Map. Если используете собственный класс как ключ Map или элемент Set, необходимо переопределить
==иhashCode, иначе сравнение идёт по идентичности объекта. - spread в Set с дубликатами не бросает исключение — дубликаты молча отбрасываются, что может маскировать логические ошибки.
- Null-ключи и null-значения. Map допускает
nullв качестве ключа и значения; обращениеmap['key']возвращаетnullкак для отсутствующего ключа, так и для явно сохранённогоnull— используйтеcontainsKeyдля разграничения.
Common mistakes
- Сводить «
List,SetиMapв Dart» к синтаксису и не объяснять sound type system. - Игнорировать жизненный цикл, основной поток или момент освобождения ресурсов в сценарии dart-20.
- Выбирать API по привычке, не проверяя состояние, ошибки, доступность и платформенные ограничения.
What the interviewer is testing
- Формулирует точную модель для «
List,SetиMapв Dart» и подтверждает ее корректным примером. - Умеет связать ответ с event loop, тестированием и отладкой на устройстве.
- Называет ограничения подхода dart-20, включая производительность, память и сопровождение.