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 .