Перейти к основному содержимому

Список против ArrayList в Java

· 4 мин. чтения

Задача: Наибольшая подстрока без повторений

Для заданной строки s, найдите длину наибольшей подстроки без повторяющихся символов. Подстрока — это непрерывная непустая последовательность символов внутри строки...

ANDROMEDA 42

1. Обзор

В этой статье мы рассмотрим различия между использованием типов List и ArrayList .

Во-первых, мы увидим пример реализации с использованием ArrayList . Затем мы переключимся на интерфейс списка и сравним различия.

2. Использование ArrayList

ArrayList — одна из наиболее часто используемых реализаций списка в Java. Он построен на основе массива, который может динамически увеличиваться и уменьшаться по мере добавления или удаления элементов. Хорошо инициализировать список начальной емкостью, когда мы знаем, что он станет большим:

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

Используя ArrayList в качестве ссылочного типа, мы можем использовать методы в ArrayList API, которых нет в List API, например, sureCapacity, trimToSize или removeRange .

2.1. Быстрый пример

Давайте напишем базовое приложение для обработки пассажиров:

public class ArrayListDemo {
private ArrayList<Passenger> passengers = new ArrayList<>(20);

public ArrayList<Passenger> addPassenger(Passenger passenger) {
passengers.add(passenger);
return passengers;
}

public ArrayList<Passenger> getPassengersBySource(String source) {
return new ArrayList<Passenger>(passengers.stream()
.filter(it -> it.getSource().equals(source))
.collect(Collectors.toList()));
}

// Few other functions to remove passenger, get by destination, ...
}

Здесь мы использовали тип ArrayList для хранения и возврата списка пассажиров. Поскольку максимальное количество пассажиров равно 20, исходная вместимость списка установлена на это значение.

2.2. Проблема с данными переменного размера

Вышеупомянутая реализация работает нормально, пока нам не нужно менять тип списка , который мы используем. В нашем примере мы выбрали ArrayList и решили, что он соответствует нашим потребностям.

Однако предположим, что по мере развития приложения становится понятно, что количество пассажиров довольно сильно варьируется. Например, если есть только пять забронированных пассажиров с начальной вместимостью 20, потери памяти составляют 75%. Допустим, мы решили переключиться на более эффективный по памяти список .

2.3. Изменение типа реализации

Java предоставляет еще одну реализацию списка , называемую LinkedList , для хранения данных переменного размера . LinkedList использует набор связанных узлов для хранения и извлечения элементов. Что, если мы решили изменить базовую реализацию с ArrayList на LinkedList :

private LinkedList<Passenger> passengers = new LinkedList<>();

Это изменение затрагивает больше частей приложения, поскольку предполагается, что все функции в демонстрационном приложении будут работать с типом ArrayList .

3. Переключение на список

Давайте посмотрим, как мы можем справиться с этой ситуацией, используя тип интерфейса List :

private List<Passenger> passengers = new ArrayList<>(20);

Здесь мы используем интерфейс List в качестве ссылочного типа вместо более конкретного типа ArrayList . Мы можем применить тот же принцип ко всем вызовам функций и типам возвращаемых значений. Например:

public List<Passenger> getPassengersBySource(String source) {
return passengers.stream()
.filter(it -> it.getSource().equals(source))
.collect(Collectors.toList());
}

Теперь рассмотрим ту же постановку задачи и изменим базовую реализацию на тип LinkedList . Классы ArrayList и LinkedList являются реализациями интерфейса List . Итак, теперь мы можем безопасно изменить базовую реализацию, не создавая помех другим частям приложения. Класс по-прежнему компилируется и работает нормально, как и раньше.

4. Сравнение подходов

Если мы используем конкретный тип списка во всей программе, то весь наш код будет без необходимости связан с этим типом списка. Это усложняет изменение типов списков в будущем.

Кроме того, служебные классы, доступные в Java, возвращают абстрактный тип, а не конкретный тип. Например, приведенные ниже служебные функции возвращают тип List :

Collections.singletonList(...), Collections.unmodifiableList(...)
Arrays.asList(...), ArrayList.sublist(...)

В частности, ArrayList.sublist возвращает тип List , даже если исходный объект имеет тип ArrayList . Таким образом, методы в List API не гарантируют возврата списка того же типа.

5. Вывод

В этой статье мы рассмотрели различия и лучшие практики использования типов List и ArrayList . ``

Мы видели, как ссылка на определенный тип может сделать приложение уязвимым для изменения в более поздний момент времени. В частности, когда базовая реализация изменяется, это влияет на другие уровни приложения. Следовательно, использование наиболее абстрактного типа (класс/интерфейс верхнего уровня) часто предпочтительнее использования определенного ссылочного типа.

Как всегда, исходный код примеров доступен на GitHub .