ArrayList — Динамический список
Знакомимся с ArrayList — самой используемой коллекцией в Java. Узнаем, почему обычные массивы ограничены и как ArrayList решает эти проблемы
Почему обычные массивы не всегда удобны
Давайте начнём с простого вопроса: зачем вообще нужны коллекции, если уже есть массивы? Массивы — это фундамент языка, и они работают отлично. Но у них есть одно серьёзное ограничение, которое вы быстро заметите на практике.
Представьте, что вы ведёте список покупок в блокноте. Выписали 5 продуктов. Но потом вспомнили ещё один. В блокноте просто допишете строку. А в массиве? Массив в Java имеет фиксированный размер. Если вы объявили массив на 5 элементов, вы не можете добавить в него шестой — Java выдаст ошибку.
Вот как это выглядит в коде:
String[] fruits = new String[3];
fruits[0] = "Яблоко";
fruits[1] = "Банан";
fruits[2] = "Вишня";
fruits[3] = "Груша"; // ArrayIndexOutOfBoundsException!
Массив размером 3 может вместить только 3 элемента. Попытка обратиться к индексу 3 приводит к исключению. Да, можно создать новый массив большего размера и скопировать туда данные, но это утомительно и неудобно.
Аналогия: массив — это как коробка с фиксированным числом ячеек. Вы купили коробку на 3 банки — и всё, шестую уже не поставите. А ArrayList — это как телескопическая полка, которая сама расширяется, когда вам нужно больше места.
Кроме фиксированного размера, у массивов есть и другие неудобства: нельзя легко удалить элемент из середины, нет встроенных методов для поиска, сортировки, фильтрации. Именно для решения всех этих проблем и была создана коллекция ArrayList.
Что такое ArrayList
ArrayList — это класс из Java Collections Framework, который реализует интерфейс List. Под капотом он использует обычный массив, но с важным отличием: когда массив заполняется, ArrayList автоматически создаёт новый массив большего размера и копирует туда все элементы. Вы этого не замечаете — ArrayList просто «умеет расти».
Представьте книжный шкаф. Если он полон, вы просто берёте шкаф побольше и переставляете книги. ArrayList делает то же самое, только автоматически и очень быстро.
Вот основные преимущества ArrayList перед обычным массивом:
- Динамический размер — список сам растёт и уменьшается по мере необходимости
- Удобные методы — добавление, удаление, поиск, сортировка встроены
- Работа с объектами — хранит ссылки на объекты, а не сами значения (хотя для примитивов есть обёртки)
- Типизация — можно указать, какой именно тип данных будет храниться
- Интеграция — работает со Stream API, методами коллекций, итераторами и многим другим
Создание ArrayList
Создать ArrayList можно несколькими способами. Давайте рассмотрим каждый из них подробно.
Способ 1: Пустой список
Самый простой способ — создать пустой список, в который вы будете добавлять элементы по мере необходимости:
ArrayList<String> names = new ArrayList<>();
Обратите внимание на угловые скобки <String> — это параметр типа (generic). Он говорит компилятору, что в этом списке будут храниться только строки. Если вы попытаетесь добавить число или другой объект, компилятор выдаст ошибку.
В Java 7 и позже можно использовать菱形 operator — два угловых скобки без типа внутри. Компилятор сам выведет тип из левой части:
ArrayList<String> names = new ArrayList<>();
Это то же самое, просто короче.
Способ 2: С начальной ёмкостью
Если вы примерно знаете, сколько элементов будет в списке, можно указать начальную ёмкость. Это оптимизация — ArrayList не будет пересоздавать массив при первых additions:
ArrayList<Integer> numbers = new ArrayList<>(100);
Это не значит, что список будет содержать 100 элементов. Он будет пустым, но под капотом уже будет выделен массив на 100 мест.
Способ 3: Из другой коллекции
Можно создать ArrayList, скопировав элементы из другой коллекции:
List<String> original = Arrays.asList("A", "B", "C");
ArrayList<String> copy = new ArrayList<>(original);
Это полезно, когда нужно создать изменяемую копию неизменяемого списка.
Способ 4: Метод List.of()
Начиная с Java 9, можно создавать неизменяемые списки одним вызовом, а затем обернуть в ArrayList:
ArrayList<String> names = new ArrayList<>(List.of("Анна", "Борис", "Вика"));
Важно: List.of() создаёт неизменяемый список. Если вы попытаетесь добавить в него элемент, получите UnsupportedOperationException. Поэтому для динамической работы лучше использовать new ArrayList<>.
Добавление элементов: метод add()
Главный метод для добавления элементов — add(). Он работает просто: ставит элемент в конец списка. Вот как это выглядит:
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList<String> fruits = new ArrayList<>();
fruits.add("Яблоко");
fruits.add("Банан");
fruits.add("Вишня");
System.out.println(fruits);
}
}
Результат: [Яблоко, Банан, Вишня]. Обратите внимание — мы вызвали add() три раза, и ArrayList каждый раз увеличивался. Никаких проблем с размером!
Метод add() можно вызвать и с указанием индекса — тогда элемент вставится не в конец, а в указанную позицию, а остальные элементы сдвинутся вправо:
ArrayList<String> fruits = new ArrayList<>();
fruits.add("Яблоко");
fruits.add("Банан");
fruits.add("Вишня");
fruits.add(1, "Груша");
System.out.println(fruits);
// [Яблоко, Груша, Банан, Вишня]
Мы вставили «Грушу» на позицию 1 (второй элемент, потому что индексация начинается с 0). «Банан» и «Вишня» автоматически сместились на одну позицию вправо.
Метод add() возвращает boolean (true), если список изменился. Это может пригодиться в некоторых ситуациях, но чаще всего возвращаемое значение просто игнорируют.
Можно добавить сразу несколько элементов, используя addAll():
ArrayList<String> fruits = new ArrayList<>();
fruits.add("Яблоко");
ArrayList<String> moreFruits = new ArrayList<>();
moreFruits.add("Банан");
moreFruits.add("Вишня");
fruits.addAll(moreFruits);
System.out.println(fruits);
// [Яблоко, Банан, Вишня]
Получение элементов: метод get()
Чтобы получить элемент по индексу, используется метод get(). Он работает так же, как обращение по индексу в массиве, но с вызовом метода:
ArrayList<String> fruits = new ArrayList<>();
fruits.add("Яблоко");
fruits.add("Банан");
fruits.add("Вишня");
String first = fruits.get(0);
String second = fruits.get(1);
String third = fruits.get(2);
System.out.println(first); // Яблоко
System.out.println(second); // Банан
System.out.println(third); // Вишня
Как и в массивах, индексация начинается с 0. Первый элемент имеет индекс 0, второй — 1, третий — 2.
Если указать несуществующий индекс, получите исключение IndexOutOfBoundsException:
ArrayList<String> fruits = new ArrayList<>();
fruits.add("Яблоко");
String item = fruits.get(5); // IndexOutOfBoundsException!
Чтобы узнать размер списка, используйте метод size():
ArrayList<String> fruits = new ArrayList<>();
fruits.add("Яблоко");
fruits.add("Банан");
System.out.println(fruits.size()); // 2
У ArrayList нет метода length, как у массива. Используйте size(). Это частая ошибка новичков — путают .length массива и .size() коллекции.
Изменение элементов: метод set()
Если нужно заменить элемент на определённой позиции, используйте метод set(). Он принимает два аргумента: индекс и новое значение:
ArrayList<String> fruits = new ArrayList<>();
fruits.add("Яблоко");
fruits.add("Банан");
fruits.add("Вишня");
System.out.println("До: " + fruits);
fruits.set(1, "Манго");
System.out.println("После: " + fruits);
// До: [Яблоко, Банан, Вишня]
// После: [Яблоко, Манго, Вишня]
Метод set() возвращает старое значение, которое было на этой позиции. Это может быть полезно, если нужно запомнить, что было до замены:
String old = fruits.set(1, "Манго");
System.out.println("Было: " + old); // Было: Банан
Удаление элементов: метод remove()
Удаление элементов — одна из сильных сторон ArrayList. Есть два варианта удаления: по индексу и по объекту.
Удаление по индексу:
ArrayList<String> fruits = new ArrayList<>();
fruits.add("Яблоко");
fruits.add("Банан");
fruits.add("Вишня");
String removed = fruits.remove(1);
System.out.println("Удалено: " + removed); // Удалено: Банан
System.out.println("Осталось: " + fruits); // Осталось: [Яблоко, Вишня]
Метод remove(int index) удаляет элемент по индексу и возвращает удалённый элемент. После удаления все элементы правее сдвигаются влево.
Удаление по объекту:
ArrayList<String> fruits = new ArrayList<>();
fruits.add("Яблоко");
fruits.add("Банан");
fruits.add("Вишня");
boolean wasRemoved = fruits.remove("Банан");
System.out.println("Удалено: " + wasRemoved); // Удалено: true
System.out.println("Осталось: " + fruits); // Осталось: [Яблоко, Вишня]
Метод remove(Object obj) удаляет первый найденный элемент, равный переданному объекту. Он возвращает boolean — true, если элемент был найден и удалён, false — если не найден.
Осторожно! Если ArrayList содержит числа (например, ArrayList<Integer>), вызов remove(1) удалит элемент по индексу 1, а не число 1. Чтобы удалить число, нужно явно привести: remove(Integer.valueOf(1)). Это классическая ловушка для новичков.
Другие полезные методы
Помимо основных CRUD-операций, ArrayList предоставляет множество других полезных методов. Давайте рассмотрим самые важные из них.
Проверка наличия элемента — contains() и indexOf()
ArrayList<String> fruits = new ArrayList<>();
fruits.add("Яблоко");
fruits.add("Банан");
fruits.add("Вишня");
boolean hasBanana = fruits.contains("Банан");
System.out.println("Есть банан: " + hasBanana); // Есть банан: true
boolean hasGrape = fruits.contains("Виноград");
System.out.println("Есть виноград: " + hasGrape); // Есть виноград: false
int index = fruits.indexOf("Вишня");
System.out.println("Индекс вишни: " + index); // Индекс вишни: 2
int notFound = fruits.indexOf("Манго");
System.out.println("Индекс манго: " + notFound); // Индекс манго: -1
Метод contains() возвращает true/false. Метод indexOf() возвращает индекс первого вхождения или -1, если элемент не найден.
Проверка пустоты — isEmpty()
ArrayList<String> fruits = new ArrayList<>();
System.out.println(fruits.isEmpty()); // true
fruits.add("Яблоко");
System.out.println(fruits.isEmpty()); // false
Очистка списка — clear()
ArrayList<String> fruits = new ArrayList<>();
fruits.add("Яблоко");
fruits.add("Банан");
System.out.println("Размер: " + fruits.size()); // 2
fruits.clear();
System.out.println("Размер: " + fruits.size()); // 0
System.out.println("Пустой: " + fruits.isEmpty()); // true
Сортировка — Collections.sort()
Сам ArrayList не имеет метода sort(), но его можно отсортировать с помощью класса Collections:
import java.util.ArrayList;
import java.util.Collections;
public class Main {
public static void main(String[] args) {
ArrayList<String> fruits = new ArrayList<>();
fruits.add("Вишня");
fruits.add("Яблоко");
fruits.add("Банан");
System.out.println("До сортировки: " + fruits);
Collections.sort(fruits);
System.out.println("После сортировки: " + fruits);
}
}
Результат: До сортировки: [Вишня, Яблоко, Банан] → После сортировки: [Банан, Вишня, Яблоко]. Элементы отсортированы в алфавитном порядке.
Переворот списка — Collections.reverse()
import java.util.ArrayList;
import java.util.Collections;
public class Main {
public static void main(String[] args) {
ArrayList<String> fruits = new ArrayList<>();
fruits.add("Яблоко");
fruits.add("Банан");
fruits.add("Вишня");
Collections.reverse(fruits);
System.out.println(fruits); // [Вишня, Банан, Яблоко]
}
}
Случайное перемешивание — Collections.shuffle()
import java.util.ArrayList;
import java.util.Collections;
public class Main {
public static void main(String[] args) {
ArrayList<String> fruits = new ArrayList<>();
fruits.add("Яблоко");
fruits.add("Банан");
fruits.add("Вишня");
Collections.shuffle(fruits);
System.out.println(fruits); // Случайный порядок
}
}
Типизация: Generic в ArrayList
Generic (параметры типа) — это механизм Java, который позволяет указать, какой тип данных будет храниться в коллекции. Без generic ArrayList может хранить что угодно, но тогда при извлечении элемента придётся делать приведение типа вручную.
// Без generic — можно добавить что угодно
ArrayList unsafe = new ArrayList();
unsafe.add("Строка");
unsafe.add(42);
unsafe.add(3.14);
// При извлечении нужно приведение типа
String s = (String) unsafe.get(0); // ОК
Integer n = (Integer) unsafe.get(1); // ОК
String x = (String) unsafe.get(2); // ClassCastException!
С generic компилятор проверяет типы на этапе компиляции:
ArrayList<String> safe = new ArrayList<>();
safe.add("Строка");
safe.add(42); // Ошибка компиляции!
Вы можете использовать ArrayList с любым ссылочным типом:
ArrayList<Integer> numbers = new ArrayList<>();
ArrayList<Double> decimals = new ArrayList<>();
ArrayList<Boolean> flags = new ArrayList<>();
ArrayList<Object> anything = new ArrayList<>();
class Car {
String brand;
int year;
Car(String brand, int year) {
this.brand = brand;
this.year = year;
}
}
ArrayList<Car> cars = new ArrayList<>();
Для примитивных типов (int, double, boolean и т.д.) используйте обёртки: Integer, Double, Boolean. Java автоматически преобразует примитивы в объекты (autoboxing) и обратно (unboxing).
Автоупаковка и распаковка
Java не может хранить примитивные типы (int, double, boolean и т.д.) напрямую в коллекциях, потому что коллекции работают только с объектами. Для решения этой проблемы Java использует механизм автоупаковки (autoboxing) и распаковки (unboxing).
Autoboxing — автоматическое преобразование примитива в объект-обёртку:
ArrayList<Integer> numbers = new ArrayList<>();
numbers.add(42); // Автоупаковка: int → Integer
double pi = 3.14;
ArrayList<Double> decimals = new ArrayList<>();
decimals.add(pi); // Автоупаковка: double → Double
ArrayList<Boolean> flags = new ArrayList<>();
flags.add(true); // Автоупаковка: boolean → Boolean
Unboxing — автоматическое преобразование объекта-обёртки обратно в примитив:
ArrayList<Integer> numbers = new ArrayList<>();
numbers.add(42);
int first = numbers.get(0); // Распаковка: Integer → int
int sum = first + 10; // 52
Вот полная таблица соответствий:
| Примитив | Обёртка |
|---|---|
| byte | Byte |
| short | Short |
| int | Integer |
| long | Long |
| float | Float |
| double | Double |
| char | Character |
| boolean | Boolean |
Автоупаковка работает не только при добавлении, но и при арифметических операциях:
ArrayList<Integer> list = new ArrayList<>();
list.add(10);
list.add(20);
int a = list.get(0); // Unboxing
int b = list.get(1); // Unboxing
int sum = a + b; // 30
list.add(sum); // Autoboxing: int → Integer
System.out.println(list); // [10, 20, 30]
Практический пример: список студентов
Давайте соберём всё вместе и создадим программу для управления списком студентов. Это покажет, как ArrayList используется в реальном коде.
import java.util.ArrayList;
import java.util.Collections;
public class StudentList {
public static void main(String[] args) {
ArrayList<String> students = new ArrayList<>();
students.add("Анна");
students.add("Борис");
students.add("Вика");
students.add("Дмитрий");
students.add("Елена");
System.out.println("Все студенты: " + students);
System.out.println("Количество: " + students.size());
students.add(2, "Григорий");
System.out.println("После добавления: " + students);
students.remove("Вика");
System.out.println("После удаления: " + students);
students.set(0, "Анна К.");
System.out.println("После изменения: " + students);
System.out.println("Есть ли Дмитрий? " + students.contains("Дмитрий"));
System.out.println("Индекс Елены: " + students.indexOf("Елена"));
Collections.sort(students);
System.out.println("В алфавитном порядке: " + students);
students.clear();
System.out.println("После очистки: " + students);
System.out.println("Пустой? " + students.isEmpty());
}
}
Эта программа демонстрирует все основные операции с ArrayList: добавление, удаление, изменение, поиск, сортировку и очистку.
Сравнение ArrayList и массива
Давайте наглядно сравним ArrayList и обычный массив, чтобы понять, когда что использовать:
| Свойство | Массив | ArrayList |
|---|---|---|
| Размер | Фиксированный | Динамический |
| Добавление в конец | Нужно знать размер | add() |
| Вставка в середину | Ручное копирование | add(index, elem) |
| Удаление | Ручное копирование | remove() |
| Поиск | Цикл вручную | contains(), indexOf() |
| Сортировка | Реализовать вручную | Collections.sort() |
| Производительность | Быстрее (нет накладных расходов) | Немного медленнее |
| Память | Компактнее | Больше overhead |
Правило выбора: Если размер известен и не меняется (например, дни недели, координаты точки), используйте массив. Если количество элементов может меняться или нужно работать с коллекциями — используйте ArrayList.
Вывод на печать ArrayList
Когда вы печатаете ArrayList с помощью System.out.println(), он автоматически вызывает метод toString(), который выводит элементы в квадратных скобках через запятую:
ArrayList<String> fruits = new ArrayList<>();
fruits.add("Яблоко");
fruits.add("Банан");
fruits.add("Вишня");
System.out.println(fruits);
// Вывод: [Яблоко, Банан, Вишня]
Для красивого вывода можно использовать цикл for-each или Stream API (мы разберём их позже):
for (String fruit : fruits) {
System.out.println("Фрукт: " + fruit);
}
// Фрукт: Яблоко
// Фрукт: Банан
// Фрукт: Вишня
Полный пример программы
Давайте создадим более сложную программу, которая демонстрирует все основные возможности ArrayList:
import java.util.ArrayList;
import java.util.Collections;
public class ArrayListDemo {
public static void main(String[] args) {
ArrayList<Integer> grades = new ArrayList<>();
grades.add(85);
grades.add(92);
grades.add(78);
grades.add(95);
grades.add(88);
System.out.println("Оценки: " + grades);
System.out.println("Размер: " + grades.size());
int sum = 0;
for (int grade : grades) {
sum += grade;
}
double average = (double) sum / grades.size();
System.out.println("Средний балл: " + average);
System.out.println("Максимальная оценка: " + Collections.max(grades));
System.out.println("Минимальная оценка: " + Collections.min(grades));
grades.remove(Integer.valueOf(78));
System.out.println("После удаления 78: " + grades);
grades.add(0, 100);
System.out.println("После добавления 100 в начало: " + grades);
Collections.sort(grades);
System.out.println("По возрастанию: " + grades);
Collections.reverse(grades);
System.out.println("По убыванию: " + grades);
System.out.println("Индекс оценки 92: " + grades.indexOf(92));
System.out.println("Содержит 95? " + grades.contains(95));
}
}
Итоги урока
- ArrayList — динамический список, который автоматически растёт и уменьшается
- Создаётся через new ArrayList<Type>() с указанием типа данных
- add(elem) — добавляет элемент в конец, add(index, elem) — вставляет по индексу
- get(index) — получает элемент по индексу
- set(index, elem) — заменяет элемент на указанной позиции
- remove(index) или remove(object) — удаляет элемент
- size() — возвращает количество элементов (не length!)
- contains(), indexOf(), isEmpty(), clear() — дополнительные полезные методы
- Generic <Type> обеспечивает типобезопасность на этапе компиляции
- Автоупаковка (autoboxing) автоматически преобразует примитивы в объекты-обёртки
- ArrayList удобнее массива для динамических данных, но массив быстрее для статических
Тест по ArrayList
5 вопросов