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

В чем разница между ArrayList и List?

2.0 Middle🔥 201 комментариев
#Коллекции

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

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

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

# Разница между ArrayList и List

Краткий ответ

List — это интерфейс (контракт), ArrayList — это реализация (конкретный класс). List определяет, ЧТО делать, ArrayList определяет, КАК это делать. При написании кода нужно использовать List, а не ArrayList.

Типы и иерархия

Иерархия наследования

Iterable
    ↓
Collection
    ↓
List (interface)  ← Определяет контракт
    ↓
ArrayList (class) ← Одна из реализаций
    ↓ (другие реализации)
LinkedList, CopyOnWriteArrayList, Vector, Stack...

Декларация типа

// ❌ Плохо — привязываемся к конкретной реализации
ArrayList<String> list = new ArrayList<>();

// ✅ Хорошо — используем интерфейс
List<String> list = new ArrayList<>();

// ✅ Ещё лучше — используем более широкий интерфейс
Collection<String> collection = new ArrayList<>();

Основное правило программирования

Программируй к интерфейсу, а не к реализации.

Почему?

  1. Гибкость — легко заменить ArrayList на LinkedList без изменения кода
  2. Тестируемость — можно использовать mock реализации
  3. Поддерживаемость — код не зависит от внутренней реализации
  4. API контракт — интерфейс документирует, что может класс

Пример: Зачем нужна абстракция

Вариант 1: Привязка к ArrayList (ПЛОХО)

public class OrderService {
    // Если понадобится LinkedList — придётся менять все методы
    public ArrayList<Order> getAllOrders() {
        ArrayList<Order> orders = new ArrayList<>();
        // ...
        return orders;
    }
    
    public void printOrders(ArrayList<Order> orders) {
        for (Order order : orders) {
            System.out.println(order);
        }
    }
}

Вариант 2: Использование List (ХОРОШО)

public class OrderService {
    // Реализация может быть любой List
    public List<Order> getAllOrders() {
        List<Order> orders = new ArrayList<>();
        // ...
        return orders;
    }
    
    // Работает с любой реализацией List
    public void printOrders(List<Order> orders) {
        for (Order order : orders) {
            System.out.println(order);
        }
    }
}

// Теперь легко переключаться между реализациями
List<Order> orders1 = service.getAllOrders();           // ArrayList
List<Order> orders2 = new LinkedList<>(orders1);       // LinkedList
List<Order> orders3 = Collections.unmodifiableList(orders1); // Immutable

Технические различия: ArrayList vs LinkedList

Структура памяти

ArrayList:

┌─────┬─────┬─────┬─────┐
│ A   │ B   │ C   │ D   │  ← элементы в памяти рядом (массив)
└─────┴─────┴─────┴─────┘

LinkedList:

┌───┐  ┌───┐  ┌───┐  ┌───┐
│ A │→→│ B │→→│ C │→→│ D │  ← элементы разбросаны по памяти (ссылки)
└───┘  └───┘  └───┘  └───┘

Сложность операций

ОперацияArrayListLinkedList
get(index)O(1) ✅O(n)
add(value)O(1) amortizedO(1) ✅
add(index, value)O(n)O(1) ✅ (если на краю)
remove(index)O(n)O(n)
remove(value)O(n)O(n)
contains(value)O(n)O(n)

Пример: Когда какой использовать

// ✅ ArrayList — если часто обращаемся по индексу
List<Product> products = new ArrayList<>();
for (int i = 0; i < products.size(); i++) {
    Product p = products.get(i);  // O(1) ✅
}

// ✅ LinkedList — если часто добавляем/удаляем в начало
List<Task> queue = new LinkedList<>();
queue.add(0, newTask);  // Вставить в начало O(1) ✅

// ✅ Но обычно используем Queue для очередей
Queue<Task> queue = new LinkedList<>();
queue.offer(newTask);   // Более читаемо

Важные методы List

Методы интерфейса List

List<String> list = new ArrayList<>();

// Добавление
list.add("A");              // Добавить в конец
list.add(1, "B");           // Вставить на позицию 1
list.addAll(other);         // Добавить всё из другого списка

// Получение
String element = list.get(0);       // Получить элемент
int index = list.indexOf("A");      // Найти индекс
boolean contains = list.contains("B");  // Проверить наличие

// Удаление
list.remove(0);             // Удалить по индексу
list.remove("A");           // Удалить по значению
list.clear();               // Очистить список

// Получение подспецификации
List<String> sublist = list.subList(0, 2);  // Представление подсписка

// Сортировка
Collections.sort(list);
list.sort(Comparator.naturalOrder());

// Итерация
for (String s : list) {
    System.out.println(s);
}

// Iterator
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    System.out.println(it.next());
}

Полиморфизм в действии

Код работает с любой реализацией List

public class ListProcessor {
    
    // Метод принимает любую реализацию List
    public static <T> List<T> removeDuplicates(List<T> input) {
        // LinkedHashSet сохраняет порядок
        return new ArrayList<>(new LinkedHashSet<>(input));
    }
    
    public static void main(String[] args) {
        // Работает с ArrayList
        List<String> list1 = new ArrayList<>(Arrays.asList("a", "b", "a"));
        List<String> result1 = removeDuplicates(list1);
        System.out.println(result1);  // [a, b]
        
        // Работает с LinkedList
        List<String> list2 = new LinkedList<>(Arrays.asList("x", "y", "x"));
        List<String> result2 = removeDuplicates(list2);
        System.out.println(result2);  // [x, y]
        
        // Работает с неизменяемым списком
        List<String> list3 = Collections.unmodifiableList(
            new ArrayList<>(Arrays.asList("p", "q", "p"))
        );
        List<String> result3 = removeDuplicates(list3);
        System.out.println(result3);  // [p, q]
    }
}

Зачем нужны другие реализации List?

CopyOnWriteArrayList

// Для многопоточности (reads > writes)
List<String> list = new CopyOnWriteArrayList<>();
list.add("A");  // Создаёт копию массива

// Безопасен для итерации из нескольких потоков
for (String s : list) {
    System.out.println(s);  // Не выбросит ConcurrentModificationException
}

Collections.unmodifiableList()

// Неизменяемый список
List<String> immutable = Collections.unmodifiableList(
    new ArrayList<>(Arrays.asList("A", "B", "C"))
);

immutable.add("D");  // UnsupportedOperationException

Arrays.asList()

// Представление массива как List
String[] array = {"A", "B", "C"};
List<String> list = Arrays.asList(array);  // Фиксированного размера!

list.add("D");  // UnsupportedOperationException
list.set(0, "X");  // OK — меняет оригинальный массив

// Правильный способ
List<String> mutableList = new ArrayList<>(Arrays.asList(array));
mutableList.add("D");  // OK

Практическое правило

// Правило: Используй САМЫЙ ШИРОКИЙ интерфейс, который нужен

// Если нужна только итерация:
Iterable<String> iter = new ArrayList<>();

// Если нужны операции Collection (size, contains):
Collection<String> coll = new ArrayList<>();

// Если нужны операции List (get, add с индексом):
List<String> list = new ArrayList<>();

// Если нужны операции очереди (offer, poll):
Queue<String> queue = new LinkedList<>();

// Если нужны операции двусторонней очереди:
Deque<String> deque = new LinkedList<>();

Частая ошибка

// ❌ ПЛОХО — слишком конкретно
public ArrayList<User> getUsers() {
    return new ArrayList<>();
}

// ✅ ХОРОШО — абстрактно
public List<User> getUsers() {
    return new ArrayList<>();
}

// ✅ ОТЛИЧНО — используем переменную как List
List<User> users = getUsers();

Итоги

  1. List — интерфейс (контракт, ЧТО)
  2. ArrayList — конкретная реализация (КАК, быстрый случайный доступ)
  3. LinkedList — альтернативная реализация (быстрая вставка/удаление)
  4. Всегда используй List в переменных и параметрах методов
  5. ArrayList выбирай только в конструкторе: new ArrayList<>();
  6. Интерфейсы дают гибкость — легко менять реализации
  7. Другие реализации List — CopyOnWriteArrayList, Vector, Collections.unmodifiableList()
В чем разница между ArrayList и List? | PrepBro