В чем разница между коллекциями Set и ArrayList в Java?

«В чем разница между коллекциями Set и ArrayList в Java?» — вопрос из категории Основы программирования, который задают на 10% собеседований QA Тестировщик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Set и ArrayList — это реализации разных интерфейсов коллекций в Java с фундаментально различными свойствами и целями использования.

Сравнительная таблица:

Характеристика ArrayList<E> (Реализация List<E>) Set<E> (Интерфейс, например, HashSet<E>, TreeSet<E>)
Основной контракт Упорядоченная последовательность. Гарантирует порядок вставки элементов. Неупорядоченное множество уникальных элементов. Гарантирует отсутствие дубликатов.
Дубликаты Разрешены. Можно добавить несколько equals()-объектов. Запрещены. Добавление элемента, который уже есть в множестве (по equals()), игнорируется.
Доступ к элементам По индексу (позиции) через get(int index). Эффективен (O(1)). Нет доступа по индексу. Можно только проверить наличие (contains()), перебрать итератором или преобразовать в массив.
Нулевые значения Разрешены. Зависит от реализации. HashSet разрешает один null, TreeSet — нет (выбрасывает NullPointerException).
Основные реализации ArrayList, LinkedList, Vector. HashSet (быстрый доступ, нет порядка), LinkedHashSet (порядок вставки), TreeSet (сортированный порядок).
Производительность contains() O(n) в худшем случае (линейный поиск). O(1) в среднем для HashSet (поиск по хэшу).

Примеры кода:

import java.util.*;

public class CollectionDifference {
    public static void main(String[] args) {
        // --- ArrayList (List) ---
        List<String> list = new ArrayList<>();
        list.add("Apple");
        list.add("Banana");
        list.add("Apple"); // Дубликат РАЗРЕШЕН
        list.add(null); // null РАЗРЕШЕН
        System.out.println("ArrayList: " + list); // [Apple, Banana, Apple, null]
        System.out.println("Элемент с индексом 1: " + list.get(1)); // "Banana"

        // --- HashSet (Set) ---
        Set<String> set = new HashSet<>();
        set.add("Apple");
        set.add("Banana");
        set.add("Apple"); // Дубликат ИГНОРИРУЕТСЯ
        set.add(null); // null РАЗРЕШЕН (для HashSet)
        System.out.println("HashSet:  " + set); // Порядок не гарантирован: [null, Apple, Banana]
        // set.get(1); // ОШИБКА! Метода get(int) не существует.
        System.out.println("Содержит 'Banana'? " + set.contains("Banana")); // true

        // --- LinkedHashSet (сохраняет порядок вставки) ---
        Set<String> linkedSet = new LinkedHashSet<>();
        linkedSet.add("Zebra");
        linkedSet.add("Apple");
        linkedSet.add("Banana");
        System.out.println("LinkedHashSet: " + linkedSet); // [Zebra, Apple, Banana]
    }
}

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

  • Используйте ArrayList (или другой List), когда:
    • Важен порядок элементов.
    • Часто требуется доступ к элементам по их позиции (индексу).
    • Допустимы или даже необходимы дублирующиеся значения.
  • Используйте Set (чаще HashSet), когда:
    • Требуется гарантировать уникальность элементов.
    • Нужна быстрая проверка наличия элемента в коллекции (contains()).
    • Порядок элементов не важен (или нужен особый: LinkedHashSet для порядка вставки, TreeSet для сортировки).