Какие основные реализации интерфейса `List` в Java?

Ответ

Ключевые реализации List<E> и их характеристики:

Реализация Внутренняя структура Ключевые особенности Сценарий использования
ArrayList Динамический массив (Object[]). Быстрый доступ по индексу (get, set) — O(1). Медленные вставка/удаление в середине (требует сдвига) — O(n). Стандартный выбор, когда преобладает доступ по индексу и перебор, а изменения — в конце списка.
LinkedList Двусвязный список. Быстрая вставка/удаление в начале/середине — O(1). Медленный доступ по индексу — O(n). Также реализует Deque. Подходит для частых вставок/удалений, особенно в начале списка, или когда нужна очередь (Queue/Deque).
Vector Аналогичен ArrayList (массив). Устарел. Все методы синхронизированы (потокобезопасны), что приводит к избыточным накладным расходам в однопоточном коде. Не рекомендуется для нового кода.
CopyOnWriteArrayList Массив. Потокобезопасна. При любой модификации создается новая копия внутреннего массива. Чтение не блокируется. Идеальна для сценариев «частое чтение, редкая запись» (например, списки слушателей событий).
Stack Наследует Vector. Устарел. Реализует LIFO-стек (методы push, pop). Не рекомендуется. Для стека используйте реализацию Deque, например, ArrayDeque.

Пример выбора:

// Для частого доступа по индексу и перебора:
List<String> catalog = new ArrayList<>();

// Для частых вставок/удалений в начале или как очередь:
List<Integer> history = new LinkedList<>();

// Для потокобезопасного списка слушателей:
CopyOnWriteArrayList<Runnable> listeners = new CopyOnWriteArrayList<>();

Ответ 18+ 🔞

А, ну ты глянь, какие у нас тут списки в Java развелись, целая коллекция, блядь! Сидишь такой, думаешь: "О, List, ну это просто, беру первое попавшееся и поехал". А потом, сука, приложение тормозит, как черепаха в патруле, и ты начинаешь понимать, что нихуя не просто. Так вот, слушай сюда, разберём по косточкам, что к чему.

ArrayList — это наш, так сказать, рабочий конь, классика жанра. Внутри у него обычный массив, который умеет растягиваться, когда места не хватает. Главный козырь — доступ по индексу за O(1), то есть моментально. Хочешь элемент под номером 999? Пиздык — и он у тебя. Но вот если тебе взбредёт впендюрить что-то в самую середину этого массива — всё, пиздец, придётся сдвигать половину элементов, это уже O(n). Идеален, когда ты в основном читаешь или добавляешь в конец. Как список покупок, который ты в основном смотришь, а не переставляешь местами молоко и хлеб каждую секунду.

LinkedList — это уже хитрая жопа. Внутри не массив, а цепочка из звеньев, где каждое знает своего соседа слева и справа. Доступ по индексу тут — просто пиздец, O(n), потому что придётся идти от начала списка, как слепой котёнок. Зато вставить или удалить элемент в любом месте — раз плюнуть, O(1), если у тебя уже есть указатель на нужное место. Плюс, он ещё и очередь (Deque) из себя корчит. Бери его, если у тебя список, который постоянно дёргают за начало и середину, или если нужна очередь типа "первым пришёл — первым ушёл".

Vector — о, это наш старый дед, который помнит времена, когда потоков было мало, а синхронизация была в моде. По сути, тот же ArrayList, но все его методы обвешаны synchronized. В однопоточном коде это просто овердохуища лишних тормозов. Сейчас его используют только те, кто застрял в 2001 году или любит пострадать. Не делай так.

CopyOnWriteArrayList — вот это, блядь, интересная штука для многопоточного ада. Её принцип — "часто читаем, редко пишем". Каждый раз, когда ты пытаешься что-то изменить (добавить, удалить), она, сука, создаёт полную новую копию всего внутреннего массива. Звучит как жесть, но зато чтение из неё никогда не блокируется и работает быстро. Идеальна для списков слушателей событий, которые все читают, а меняют раз в год.

Stack — наследник того самого Vector, должен был быть стеком (последний зашёл, первый вышел). Но он тоже устарел, как ламповый телевизор. Для стека сейчас все умные люди берут ArrayDeque — он и быстрее, и современнее.

Короче, вот тебе пример, чтобы не ебать мозги:

// Каталог товаров — ты в основном листаешь и смотришь, редко меняешь.
List<String> catalog = new ArrayList<>();

// История действий — постоянно что-то добавляется в начало, старьё удаляется.
List<Integer> history = new LinkedList<>();

// Список слушателей на кнопке — 100500 потоков их читают, а добавляют нового раз в неделю.
CopyOnWriteArrayList<Runnable> listeners = new CopyOnWriteArrayList<>();

Выбирай с умом, а то получится как с той Муму — вроде доброе дело хотел, а в итоге всех утопил в производительности.