← Назад к вопросам

Как дела с коллекциями

1.3 Junior🔥 241 комментариев
#Коллекции#Основы Java

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

# Как дела с коллекциями в Java

Коллекции — фундамент Java программирования. Рассмотрю их подробно и когда использовать каждую.

1. Иерархия коллекций

Collection
├── List (упорядоченные, могут быть дубликаты)
│   ├── ArrayList (массив, быстрый random access)
│   ├── LinkedList (связный список, быстрые add/remove в начало)
│   └── CopyOnWriteArrayList (потокобезопасная)
├── Set (без дубликатов, неупорядоченные)
│   ├── HashSet (O(1) поиск)
│   ├── TreeSet (отсортирована, O(log n))
│   └── LinkedHashSet (сохраняет порядок вставки)
└── Queue (FIFO/LIFO)
    ├── LinkedList (FIFO)
    ├── PriorityQueue (по приоритету)
    └── Deque (оба конца)

Map (ключ-значение)
├── HashMap (O(1) поиск)
├── TreeMap (отсортирована, O(log n))
├── LinkedHashMap (сохраняет порядок)
└── ConcurrentHashMap (потокобезопасная)

2. Выбор нужной коллекции

ArrayList — дефолтный выбор

List<String> names = new ArrayList<>();
names.add("Иван");
names.add("Мария");
String first = names.get(0); // O(1)

Когда использовать:

  • Нужен быстрый доступ по индексу
  • Больше чтений, чем добавлений в начало

Недостатки:

  • Медленное удаление из начала/конца
  • Нужно увеличивать массив при переполнении

LinkedList — для очередей

Queue<String> queue = new LinkedList<>();
queue.add("first");
queue.add("second");
String polled = queue.poll(); // O(1)

Когда использовать:

  • Нужна очередь (FIFO) или стек (LIFO)
  • Много добавлений в конец и удаления из начала

Недостатки:

  • Медленный random access O(n)
  • Дополнительная память на связи

HashSet — для уникальных значений

Set<Integer> uniqueIds = new HashSet<>();
uniqueIds.add(1);
uniqueIds.add(2);
uniqueIds.add(1); // Не добавится, уже есть

boolean contains = uniqueIds.contains(1); // O(1)

Когда использовать:

  • Нужны уникальные значения
  • Нужна O(1) проверка наличия

Внимание:

  • equals() и hashCode() должны быть правильными!
class User {
    private Long id;
    
    @Override
    public boolean equals(Object o) {
        if (!(o instanceof User)) return false;
        return id.equals(((User) o).id);
    }
    
    @Override
    public int hashCode() {
        return id.hashCode();
    }
}

TreeSet — отсортированное множество

Set<Integer> sorted = new TreeSet<>();
sorted.add(3);
sorted.add(1);
sorted.add(2);
// Итерация: 1, 2, 3 (автоматически отсортировано)

Когда использовать:

  • Нужны отсортированные данные
  • Нужны операции как first(), last(), subSet()

Недостатки:

  • O(log n) для add/remove/contains
  • Нужен Comparable или Comparator

HashMap — основная карта

Map<String, Integer> userAges = new HashMap<>();
userAges.put("Иван", 30);
userAges.put("Мария", 25);

if (userAges.containsKey("Иван")) {
    System.out.println(userAges.get("Иван")); // 30
}

userAges.forEach((name, age) -> 
    System.out.println(name + ": " + age)
);

Когда использовать:

  • Нужны пары ключ-значение
  • O(1) поиск и вставка

Внимание: Не потокобезопасна!

ConcurrentHashMap — потокобезопасная

Map<String, Integer> counts = new ConcurrentHashMap<>();
counts.put("key1", 1);

// Безопасна из разных потоков
counts.computeIfPresent("key1", (k, v) -> v + 1);

Когда использовать:

  • Многопоточное приложение
  • Нужна высокая производительность

3. Операции над коллекциями

Потоковая обработка (Streams)

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

// Фильтрация
List<Integer> even = numbers.stream()
    .filter(n -> n % 2 == 0)
    .collect(Collectors.toList()); // [2, 4]

// Трансформация
List<String> strings = numbers.stream()
    .map(n -> "num" + n)
    .collect(Collectors.toList()); // [num1, num2, ...]

// Группировка
Map<Integer, List<Integer>> grouped = numbers.stream()
    .collect(Collectors.groupingBy(n -> n % 2)); // 0 -> [2, 4], 1 -> [1, 3, 5]

// Поиск
Optional<Integer> first = numbers.stream()
    .filter(n -> n > 3)
    .findFirst(); // Optional(4)

// Расчёты
int sum = numbers.stream()
    .mapToInt(Integer::intValue)
    .sum(); // 15

Итерирование

List<String> items = Arrays.asList("a", "b", "c");

// for-each (рекомендуется)
for (String item : items) {
    System.out.println(item);
}

// iterator (для удаления)
Iterator<String> iterator = items.iterator();
while (iterator.hasNext()) {
    String item = iterator.next();
    if (item.equals("b")) {
        iterator.remove(); // Безопасно удаляет
    }
}

// forEach (функциональный)
items.forEach(System.out::println);

4. Производительность

ОперацияArrayListLinkedListHashSetTreeSetHashMap
Get(index)O(1)O(n)---
AddO(1)*O(1)O(1)*O(log n)O(1)*
RemoveO(n)O(1)O(1)O(log n)O(1)
ContainsO(n)O(n)O(1)O(log n)O(1)

*Амортизированная сложность

5. Частые ошибки

Ошибка 1: Неправильный equals/hashCode

// ПЛОХО - HashSet не работает правильно
class User {
    private String name;
    // Нет equals/hashCode!
}

// ХОРОШО
class User {
    private String name;
    
    @Override
    public boolean equals(Object o) {
        return o instanceof User && name.equals(((User) o).name);
    }
    
    @Override
    public int hashCode() {
        return name.hashCode();
    }
}

Ошибка 2: ConcurrentModificationException

// ПЛОХО - исключение!
List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
for (String item : list) {
    if (item.equals("b")) {
        list.remove(item); // ConcurrentModificationException
    }
}

// ХОРОШО
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    if (it.next().equals("b")) {
        it.remove(); // Безопасно
    }
}

Ошибка 3: Синхронизация

// ПЛОХО - при многопоточности
Map<String, Integer> map = new HashMap<>();

// ХОРОШО - потокобезопасно
Map<String, Integer> map = Collections.synchronizedMap(new HashMap<>());
// или ещё лучше
Map<String, Integer> map = new ConcurrentHashMap<>();

6. Рекомендации

  1. Начинайте с ArrayList/HashMap — это дефолтный выбор в 90% случаев
  2. Используйте Set для уникальности — просто и эффективно
  3. Проверяйте equals/hashCode — это источник множества ошибок
  4. Потокобезопасность — используйте Concurrent* версии или Collections.synchronized*
  5. Streams — модный способ, но иногда простой цикл понятнее
  6. Профилируйте — выбирайте структуру данных по реальным характеристикам нагрузки
Как дела с коллекциями | PrepBro