User Professor Hans Noodles
Professor Hans Noodles
41 уровень

Класс ArrayList

Статья из группы Java Developer
Привет! В прошлых лекциях мы подробно разобрали такую структуру данных как массив и рассмотрели распространенные примеры работы с ними. Но у этой структуры данных есть ряд недостатков. Ответом на них в Java стало появление ArrayList. Если говорить максимально просто, то список ArrayList — это “прокачанный” массив с большим количеством новых возможностей. Класс ArrayList - 1

Чем Arraylist отличается от обычных массивов?

Вообще, массивы — штука достаточно удобная и как ты уже заметил, с ними можно много чего делать :) Тем не менее, у массивов есть и ряд недостатков.
  • Ограниченный размер. Нужно уже на этапе создания массива знать, сколько ячеек он должен содержать. Недооценишь нужное количество — места не хватит. Переоценишь — массив останется полупустым, и это еще полбеды. Ведь получается, ты еще и выделишь под него больший объем памяти, чем нужно.
  • У массива нет методов для добавления элементов. Всегда приходится явно указывать индекс ячейки, куда нужно добавить элемент. Если нечаянно указать уже занятую ячейку с каким-то нужным значением, оно перезапишется.
  • Нет методов для удаления элемента. Значение можно только “обнулить”.

public class Cat {

   private String name;

   public Cat(String name) {
       this.name = name;
   }

   public static void main(String[] args) {

       Cat[] cats = new Cat[3];
       cats[0] = new Cat("Томас");
       cats[1] = new Cat("Бегемот");
       cats[2] = new Cat("Филипп Маркович");

       cats[1] = null;

      
      
       System.out.println(Arrays.toString(cats));
   }

   @Override
   public String toString() {
       return "Cat{" +
               "name='" + name + '\'' +
               '}';
   }
}
Вывод:

[Cat{name='Томас'}, null, Cat{name='Филипп Маркович'}]
Все эти недочеты можно устранить, используя ArrayList. Создается он очень просто:

ArrayList<Cat> cats = new ArrayList<Cat>();
Теперь мы создали список для хранения объектов Cat. Обрати внимание: мы не указываем размер ArrayList’a, поскольку он является автоматически расширяемым. Как такое возможно? Легко. Ты удивишься, но в основе ArrayList’a лежит самый обыкновенный массив :) Да, внутри у него находится массив, в котором и хранятся наши элементы. Но у ArrayList’a есть специальный механизм по работе с ним:
  • Когда этот внутренний массив заполняется, ArrayList создает внутри себя новый массив. Его размер = (размер старого массива * 1,5) +1.
  • Все данные копируются из старого массива в новый
  • Старый массив удаляется сборщиком мусора.
Благодаря этому механизму в ArrayList’e (в отличие от массива) реализован метод для добавления нового элемента. Это метод add().

public static void main(String[] args) {

   ArrayList<Cat> cats = new ArrayList<Cat>();
   cats.add(new Cat("Бегемот"));
}
Новый элемент добавляется в конец списка. Теперь риска переполнения нет, поэтому такой механизм полностью безопасен. Кстати, ArrayList умеет не только искать объект по индексу, но и наоборот — может найти индекс объекта в ArrayList’e по ссылке на объект! Для этого в нем реализован метод indexOf(): Мы передаем в него ссылку на нужный объект, и indexOf() возвращает нам его индекс:

public static void main(String[] args) {

   ArrayList<Cat> cats = new ArrayList<>();
   Cat thomas = new Cat("Томас");
   Cat behemoth = new Cat("Бегемот");
   Cat philipp = new Cat("Филипп Маркович");
   Cat pushok = new Cat("Пушок");

   cats.add(thomas);
   cats.add(behemoth);
   cats.add(philipp);
   cats.add(pushok);

   int thomasIndex = cats.indexOf(thomas);
   System.out.println(thomasIndex);
}
Вывод:

0
Все верно, объект thomas действительно хранится в ячейке 0. У массивов есть не только недостатки, но и несомненные преимущества. Одно из них — поиск элемента по индексу. Поскольку мы указываем на индекс, то есть на конкретный адрес в памяти, такой поиск в массиве осуществляется очень быстро. ArrayList в Java тоже так умеет! Для этого в нем реализован метод get():

public static void main(String[] args) {

   ArrayList<Cat> cats = new ArrayList<>();
   Cat thomas = new Cat("Томас");
   Cat behemoth = new Cat("Бегемот");
   Cat philipp = new Cat("Филипп Маркович");
   Cat pushok = new Cat("Пушок");

   cats.add(thomas);
   cats.add(behemoth);
   cats.add(philipp);
   cats.add(pushok);

   Cat secondCat = cats.get(1);

   System.out.println(secondCat);
}
Вывод:

Cat{name='Бегемот'}
Кроме того, можно легко узнать, содержит ли ArrayList какой-то конкретный объект, или нет. Это делается с помощью метода contains():

public static void main(String[] args) {

   ArrayList<Cat> cats = new ArrayList<>();
   Cat thomas = new Cat("Томас");
   Cat behemoth = new Cat("Бегемот");
   Cat philipp = new Cat("Филипп Маркович");
   Cat pushok = new Cat("Пушок");

   cats.add(thomas);
   cats.add(behemoth);
   cats.add(philipp);
   cats.add(pushok);

   cats.remove(pushok);
   System.out.println(cats.contains(pushok));
}
Метод проверяет содержится ли элемент во внутреннем массиве ArrayList’a, и возвращает результат в виде booleantrue или false. Вывод:

false
И еще важное по поводу вставки. Класс ArrayList - 2ArrayList позволяет вставлять данные не только в конец массива, но и в любую ячейку по индексу. Для этого у него есть два метода:
  • add(int index, Cat element)
  • set(int index, Cat element)
В оба ты передаешь индекс ячейки, в которую нужно сделать вставку, и ссылку на сам объект. Разница в том, что вставка через set() затирает старое значение, хранящееся в ячейке. А вставка через add() сначала сдвигает все элементы начиная с [index] к концу массива, а в образовавшуюся пустую ячейку добавляет нужный тебе объект. Вот пример:

public static void main(String[] args) {

   ArrayList<Cat> cats = new ArrayList<>();
   Cat thomas = new Cat("Томас");
   Cat behemoth = new Cat("Бегемот");
   Cat philipp = new Cat("Филипп Маркович");
   Cat pushok = new Cat("Пушок");

   cats.add(thomas);
   cats.add(behemoth);

   System.out.println(cats.toString());

   cats.set(0, philipp);//Сейчас у нас список из 2 котов. Добавляем 3-го через set:

   System.out.println(cats.toString());
}
Вывод:

[[Cat{name='Томас'}, Cat{name='Бегемот'}]
[Cat{name='Филипп Маркович'}, Cat{name='Бегемот'}]
У нас был список из 2 котов, мы вставили еще одного через метод set() в ячейку 0. В итоге старое значение, хранящееся в этой ячейке, было заменено на новое.

public static void main(String[] args) {

   ArrayList<Cat> cats = new ArrayList<>();
   Cat thomas = new Cat("Томас");
   Cat behemoth = new Cat("Бегемот");
   Cat philipp = new Cat("Филипп Маркович");
   Cat pushok = new Cat("Пушок");

   cats.add(thomas);
   cats.add(behemoth);

   System.out.println(cats.toString());

   cats.add(0, philipp);//Сейчас у нас список из 2 котов. Добавляем 3-го через add

   System.out.println(cats.toString());
}
А вот add() сработал иначе. Он сдвинул все элементы вправо и после этого записал новое значение в ячейку 0. Вывод:

[Cat{name='Томас'}, Cat{name='Бегемот'}]
[Cat{name='Филипп Маркович'}, Cat{name='Томас'}, Cat{name='Бегемот'}]
Чтобы полностью очистить список, используется метод clear():

public static void main(String[] args) {

   ArrayList<Cat> cats = new ArrayList<>();
   Cat thomas = new Cat("Томас");
   Cat behemoth = new Cat("Бегемот");
   Cat philipp = new Cat("Филипп Маркович");
   Cat pushok = new Cat("Пушок");

   cats.add(thomas);
   cats.add(behemoth);
   cats.add(philipp);
   cats.add(pushok);

   cats.clear();

   System.out.println(cats.toString());
}
Вывод:

[]
Из списка было удалено все содержимое. Кстати, обрати внимание: в отличие от массивов, в ArrayList метод toString() переопределен и сразу выводит список в формате строки. В случае с массивами нам для этого приходилось использовать класс Arrays. И раз уж вспомнили о Arrays: в Java можно легко “переключаться” между массивом и ArrayList, то есть преобразовывать одно к другому. В классе Arrays для этого есть метод Arrays.asList(). С его помощью мы получаем содержимое массива в виде списка и передаем его в конструктор нашего ArrayList:

public static void main(String[] args) {

   Cat thomas = new Cat("Томас");
   Cat behemoth = new Cat("Бегемот");
   Cat philipp = new Cat("Филипп Маркович");
   Cat pushok = new Cat("Пушок");

   Cat[] catsArray = {thomas, behemoth, philipp, pushok};

   ArrayList<Cat> catsList = new ArrayList<>(Arrays.asList(catsArray));
   System.out.println(catsList);
}
Вывод:

[Cat{name='Томас'}, Cat{name='Бегемот'}, Cat{name='Филипп Маркович'}, Cat{name='Пушок'}]
Можно сделать и наоборот — получить массив из объекта ArrayList. Для этого используется метод toArray():

public static void main(String[] args) {

   ArrayList<Cat> cats = new ArrayList<>();

   Cat thomas = new Cat("Томас");
   Cat behemoth = new Cat("Бегемот");
   Cat philipp = new Cat("Филипп Маркович");
   Cat pushok = new Cat("Пушок");

   cats.add(thomas);
   cats.add(behemoth);
   cats.add(philipp);
   cats.add(pushok);

   Cat[] catsArray = cats.toArray(new Cat[0]);

   System.out.println(Arrays.toString(catsArray));
}
Обрати внимание: в метод toArray() мы передали пустой массив. Это не ошибка. Внутри класса ArrayList данный метод реализован таким образом, что передача пустого массива увеличивает его производительность. Пока просто запомни это на будущее (но передать какой-то конкретный размер тоже можно, будет работать). Кстати про размер. Текущий размер списка можно узнать при помощи метода size():

public static void main(String[] args) {

   ArrayList<Cat> cats = new ArrayList<>();


   Cat thomas = new Cat("Томас");
   Cat behemoth = new Cat("Бегемот");
   Cat philipp = new Cat("Филипп Маркович");
   Cat pushok = new Cat("Пушок");

   cats.add(thomas);
   cats.add(behemoth);
   cats.add(philipp);
   cats.add(pushok);

   System.out.println(cats.size());
}
Здесь важно понимать, что в отличии от свойства length массива, метод ArrayList.size() возвращает именно число элементов, а не изначальную вместимость, ведь ее мы при создании ArrayList не указываем. Кстати, указать ее в общем-то можно. У ArrayList есть соответствующий конструктор. Но его поведение в плане добавления новых элементов от этого не изменится:

public static void main(String[] args) {

   ArrayList<Cat> cats = new ArrayList<>(2);//создаем ArrayList с изначальной вместимостью 2


   Cat thomas = new Cat("Томас");
   Cat behemoth = new Cat("Бегемот");
   Cat philipp = new Cat("Филипп Маркович");
   Cat pushok = new Cat("Пушок");

   cats.add(thomas);
   cats.add(behemoth);
   cats.add(philipp);
   cats.add(pushok);

   System.out.println(cats.size());
}
Вывод в консоль:

4
Мы создали список на 2 элемента, но когда нам это понадобилось, он спокойно расширился. Другое дело, что если мы создали изначально очень маленький список, ему придется чаще проводить операцию расширения, а это затрачивает определенное количество ресурсов. В этой лекции мы почти не затронули процесс удаления элементов из ArrayList. Конечно, это не по причине забывчивости. Эта тема была выделена в отдельную лекцию, с которой ты сможешь ознакомиться дальше :)
Комментарии (163)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Сергей Коваленко Уровень 15, Краснодар
27 ноября 2021
Призадумался У переменной массива тип - ссылка. Значит должен быть и класс, к которому она принадлежит. Если мы у переменной массива будем вызывать методы, например .hashCode() или .equals() или .toString(), то такой код будет отлично срабатывать. Но если попробуем узнать какой класс:

        int[] array = new int[0];
        System.out.println(array.getClass());

        byte[] bytes = new byte[0];
        System.out.println(bytes.getClass());

        double[] doubles = new double[0];
        System.out.println(doubles.getClass());

        Integer[] integers = new Integer[0];
        System.out.println(integers.getClass());

        Object[] objects = new Object[0];
        System.out.println(objects.getClass());

        String[] strings = new String[0];
        System.out.println(strings.getClass());
то получим такой вывод:

class [I
class [B
class [D
class [Ljava.lang.Integer;
class [Ljava.lang.Object;
class [Ljava.lang.String;
Как видно из вывода класс у переменных определяется как: для примитивов int[ ] как [I, для byte[ ] как [B, для double[ ] как [D для ссылочных типов добавляется префикс [L. Похоже на какой то гибридный тип данных или костыль в синтаксисе. Либо это означает, что в коде могут быть ссылки, которые не принадлежат ни к каким к классам, но как-то наследуют от Object стандартный набор методов для работы с ними. Подскажите где про это почитать, чет не нагуглил.
Nataly Novikova Уровень 15, Stavropol, Russian Federation
24 ноября 2021
Таак, терерь все легло по полочкам, спаааасибоооо
Кот Уровень 8, Минск
18 сентября 2021
ArrayList<Cat> cats = new ArrayList<Cat>(); Вроде бы с java 7 можно не указывать тип значения после new. То есть не ArrayList<Cat>(), a просто ArrayList<>();
VLB Уровень 25, Минск, Беларусь
19 августа 2021
Добрый день, в лекции пишут следующее: Вот пример: public static void main(String[] args) { ArrayList<Cat> cats = new ArrayList<>(); Cat thomas = new Cat("Томас"); Cat behemoth = new Cat("Бегемот"); Cat philipp = new Cat("Филипп Маркович"); Cat pushok = new Cat("Пушок"); cats.add(thomas); cats.add(behemoth); System.out.println(cats.toString()); cats.set(0, philipp);//Сейчас у нас список из 2 котов. Добавляем 3-го через set: System.out.println(cats.toString()); } Вывод: [[Cat{name='Томас'}, Cat{name='Бегемот'}] [Cat{name='Филипп Маркович'}, Cat{name='Бегемот'}] Но у меня, когда я набрал этот код в Эклипс выдает результат [work_18_08_2021.Cat@1175e2db, work_18_08_2021.Cat@36aa7bc2] [work_18_08_2021.Cat@53bd815b, work_18_08_2021.Cat@36aa7bc2] подскажите пжлст где ошибка?
Anonymous #2701131 Уровень 9
10 июля 2021
Мм, подскажите, пожалуйста, вот почти в каждой лекции есть отссылочки на предыдущие(типо в прошлой лекции мы подробно разобрали массивы...). Как эти лекции посмотреть в их хронологической последовательности?
Jack Daniel Уровень 28, Минск
12 июня 2021
Хорошая лекция.
K. Уровень 27, Москва
19 мая 2021
Насчёт этого абзаца: Да, внутри у него находится массив, в котором и хранятся наши элементы. Но у ArrayList’a есть специальный механизм по работе с ним: Когда этот внутренний массив заполняется, ArrayList создает внутри себя новый массив. Его размер = (размер старого массива * 1,5) +1. Все данные копируются из старого массива в новый Старый массив удаляется сборщиком мусора. Кажется, тут объединили два понятия. Размер нового массива = размер старого массива * 1,5. А количество элементов в новом списке = количество нынешних элементов + 1
Anthon Petrow Уровень 11, Якутск, Россия
18 апреля 2021
на 12 строчке про метод asList опечатка. В начале main мы создали массив ArrayList, но так и не использовали его, так как на 12 строчке создается новая коллекция, почему? Думаю надо было так

cats.Arrays.asList(catsArray);
Дмитрий Кохна Уровень 12, Минск, Беларусь
20 февраля 2021
Вопрос - как получить индекс в списке используя "indexOf", если добавить объект в список как list.add(new Car("aaa"))? И тот же вопрос по "contains".
Skifan Уровень 8, Санкт-Петербург, Россия
19 февраля 2021
Как всегда. Читаешь, вроде все понятно. Начинаешь решать задачки, них""" не понятно