Вопрос 30 · Раздел 4

Какие операции поддерживает интерфейс Collection?

Если проект на Java 8 — используйте coll.toArray(new String[0]). JVM оптимизирует это так же эффективно.

Версии по языкам: English Russian Ukrainian

🟢 Junior Level

Collection — базовый интерфейс для List, Set, Queue.

⚠️ НЕ путать: Collection (без ‘s’) — интерфейс (List, Set, Queue). Collections (с ‘s’) — утилитарный класс с static-методами (sort, synchronizedList, unmodifiableList). Это разные вещи!

Основные методы:

Collection<String> coll = new ArrayList<>();

// Добавление
coll.add("A");
coll.addAll(List.of("B", "C"));

// Проверка
coll.contains("A");       // true
coll.containsAll(...);    // true
coll.isEmpty();           // false
coll.size();              // 3

// Удаление
coll.remove("A");
coll.removeAll(...);
coll.clear();

// Преобразование
Object[] arr = coll.toArray();
String[] strArr = coll.toArray(String[]::new);  // Java 11+

🟡 Middle Level

Группы методов

Модификация:

add(e)           // → true если изменилась
addAll(c)        // → true если изменилась
remove(e)        // → true если удалил
removeAll(c)     // Удалить все из c
retainAll(c)     // Оставить только из c (пересечение)
clear()          // Очистить

Java 8+ функциональные:

// removeIf — удаление по условию
list.removeIf(e -> e.startsWith("A"));  // O(n) для ArrayList!

// stream / parallelStream
coll.stream()
    .filter(e -> e.length() > 3)
    .map(String::toUpperCase)
    .collect(Collectors.toList());

Сложность contains

// ArrayList: O(n)
list.contains("A");  // Перебор!

// HashSet: O(1)
set.contains("A");   // Хеш!

// containsAll для двух списков: O(n × m)!
list1.containsAll(list2);  // Для каждого из list2 перебор list1!

toArray

// Java 11+: самый современный способ
String[] arr = list.toArray(String[]::new);

// Старый способ (тоже работает)
String[] arr = list.toArray(new String[0]);
// → JVM оптимизирует, не нужно создавать массив нужного размера!

Если проект на Java 8 — используйте coll.toArray(new String[0]). JVM оптимизирует это так же эффективно.


🔴 Senior Level

Когда НЕ использовать parallelStream

  1. Коллекция < 10K элементов — overhead форка > выгода
  2. Операции быстрые (арифметика, getters)
  3. Порядок важен — parallelStream не гарантирует порядок
  4. Есть side-эффекты — race conditions

removeIf оптимизация

// ArrayList.removeIf:
// 1. Находит первый элемент для удаления
// 2. До него — НЕ трогает
// 3. После — ОДИН System.arraycopy
// → O(n), минимальные записи!

// VS iterator.remove() в цикле:
// → System.arraycopy КАЖДЫЙ раз
// → O(n²)!

Bulk operations сложность

// list1.removeAll(list2)
// Если list2 = ArrayList: O(n × m)
// Если list2 = HashSet: O(n + m)

// → Всегда конвертируйте в Set для bulk операций!
Set<String> set = new HashSet<>(list2);
list1.removeAll(set);  // O(n + m)

Для средних и больших коллекций — конвертация в Set выгодна. Для маленьких (< 10 элементов) overhead создания HashSet может превысить выгоду.

Spliterator характеристики

Spliterator<String> spliterator = coll.spliterator();

// Флаги оптимизации:
// IMMUTABLE  → коллекция не меняется
// CONCURRENT → можно менять параллельно
// DISTINCT   → все элементы уникальны
// SORTED     → отсортировано
// SIZED      → известен точный размер

// Stream API использует эти флаги!
set.stream()
   .distinct()  // → Ничего не делает! Уже DISTINCT
   .count();    // → O(1) только когда Stream знает точный размер и нет операций, изменяющих количество элементов. distinct() всё равно требует обхода.

GC Pressure

// toArray() создаёт КОПИЮ всех данных!
Object[] copy = collection.toArray();  // O(n) память

// Для больших коллекций → GC давление
// → Используйте stream вместо копирования
collection.stream().forEach(this::process);

toArray создаёт полную копию. Избегайте если: (1) Коллекция > 100K элементов (GC pressure). (2) Вы только читаете (используйте stream/forEach). Допустимо если: (1) Нужен массив для legacy API. (2) Коллекция маленькая.

Production Experience

Реальный сценарий: containsAll O(n²)

// 10,000 элементов в обоих списках
list1.containsAll(list2);  // 10,000 × 10,000 = 100 млн операций!

// Решение:
Set<String> set = new HashSet<>(list2);
list1.containsAll(set);  // 10,000 × O(1) = 10,000 операций
// → Ускорение в 10,000 раз!

Best Practices

  1. removeIf → O(n) для ArrayList
  2. toArray(T[]::new) → Java 11+ стиль
  3. containsAll с Set → O(n+m) вместо O(n×m)
  4. Spliterator характеристики → оптимизации Stream
  5. Избегайте toArray для больших коллекций (GC)
  6. retainAll = пересечение множеств
  7. addAll = объединение

Резюме для Senior

  • removeIf = O(n), оптимизирован внутри
  • Bulk ops → конвертируйте в Set
  • toArray → копия, GC pressure
  • Spliterator → флаги для Stream оптимизаций
  • contains → O(n) для List, O(1) для Set
  • Java 11+ → toArray(T[]::new)

🎯 Шпаргалка для интервью

Обязательно знать:

  • Collection (интерфейс) != Collections (утилитарный класс) — не путать на интервью
  • Базовые группы: модификация (add, remove, retainAll), проверка (contains, isEmpty), преобразование (stream, toArray)
  • removeIf() — O(n) для ArrayList (один System.arraycopy), лучше iterator.remove() O(n^2)
  • containsAll/removeAll для двух ArrayList = O(n × m); конвертация одного в Set = O(n + m)
  • toArray(T[]::new) (Java 11+) — современный стиль; JVM оптимизирует new T[0] так же эффективно
  • toArray() создаёт полную копию — GC pressure для больших коллекций (>100K элементов)
  • Spliterator флаги (DISTINCT, SORTED, SIZED) — внутренние оптимизации Stream API

Частые уточняющие вопросы:

  • Почему containsAll двух списков из 10K элементов = 100 млн операций? — Для каждого элемента list2 перебирается list1: 10K × 10K. Решение: new HashSet(list2).
  • Когда parallelStream НЕ стоит использовать? — Коллекция < 10K элементов, быстрые операции, важен порядок, есть side-эффекты.
  • Зачем конвертировать в Set перед removeAll? — HashSet.contains = O(1); вместо O(n × m) получаем O(n + m).
  • Что делают флаги Spliterator? — Подсказки Stream API для оптимизации: DISTINCT — distinct() noop, SIZED — count() = O(1).

Красные флаги (НЕ говорить):

  • «Collection и Collections — одно и то же» — нет, Collection = интерфейс, Collections = утилитарный класс
  • «contains для ArrayList = O(1)» — нет, O(n), перебор всех элементов
  • «toArray() не создаёт копию» — нет, это полная копия данных в новый массив
  • «parallelStream всегда быстрее обычного stream» — нет, overhead форка для малых коллекций перевешивает выгоду

Связанные темы:

  • [[28. Как правильно удалять элементы во время итерации]]
  • [[23. Что такое Collections.unmodifiableList()]]
  • [[26. Что такое fail-fast и fail-safe итераторы]]