Перейти к содержимому
Теория · Коллекции и обобщения · урок 1 из 13

Обобщения: List<String>

Чему вы научитесь

Чему вы научитесь:

  • заводить список List<String> и объяснять, что даёт тип в угловых скобках;
  • добавлять, менять и удалять элементы ArrayList и обходить список, не ломая его;
  • объяснять, как HashSet и HashMap находят элемент через hashCode и equals;
  • сортировать список своих объектов через Comparable и Collections.sort.

Представьте гардероб в театре. Гардеробщик не знает заранее, сколько придёт зрителей: десять или триста. Поэтому шапки он кладёт на длинную полку, где хватит места всем, а пальто вешает на крючки с номерами.

Массив из темы о массивах для такого гардероба не годится. Это ряд шкафчиков, число которых задают один раз, при создании. Пришло больше гостей — нужен новый массив, и все вещи придётся перекладывать вручную.

Для таких задач в Java есть коллекции. Коллекция — объект, который хранит группу других объектов и сам растёт, когда их становится больше. Вся эта тема о коллекциях, и начнём мы со списка:

JAVA
import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> guests = new ArrayList<>();
        guests.add("Anna");
        guests.add("Boris");
        System.out.println(guests);        // [Anna, Boris]
        System.out.println(guests.size()); // 2
        String first = guests.get(0);
        System.out.println(first);         // Anna
    }
}

Давайте разберём программу по строкам:

  1. Две строки import подключают имена ArrayList и List из пакета java.util, где живут коллекции.
  2. List<String> guests = new ArrayList<>(); создаёт пустой список строк.
  3. guests.add("Anna") кладёт строку в конец списка. Список растёт сам: его размер заранее никто не задавал.
  4. System.out.println(guests) печатает элементы в квадратных скобках: [Anna, Boris].
  5. guests.size() возвращает число элементов, 2. У массива это поле length, а у списка — метод size().
  6. guests.get(0) возвращает элемент с индексом 0. Индексы, как и у массива, начинаются с нуля.

Тип в угловых скобках

Запись List<String> читается как «список строк». В угловых скобках стоит тип элементов, и он входит в тип самой переменной. Классы и интерфейсы, которые принимают тип как параметр, называют обобщениями. Тип в угловых скобках — это параметр типа.

Вернёмся в гардероб. Параметр типа — табличка над полкой: «Только шапки». Компилятор следит за табличкой строже любого гардеробщика и не даст положить на полку зонт:

JAVA
List<String> guests = new ArrayList<>();
guests.add(42); // ✗
TEXT
error: incompatible types: int cannot be converted to String

Ошибку ловит компилятор, ещё до запуска. Зато всё, что лежит в List<String>, точно строка. Поэтому guests.get(0) сразу возвращает String, и приводить тип вручную не нужно.

Справа, в new ArrayList<>(), угловые скобки пустые: тип компилятор сам берёт из левой части. Запись new ArrayList<String>() тоже верна, но длиннее.

List и ArrayList

Слева стоит List, а справа ArrayList, и это не опечатка. List — интерфейс, как Courier из темы об интерфейсах. Он перечисляет, что умеет любой список: add, get, size и другие методы. ArrayList — класс, который реализует этот интерфейс и хранит элементы в массиве.

Переменную объявляют с типом интерфейса, а конкретный класс называют только после new. Если позже понадобится другой список, поменяется одно слово справа, а код, который работает с guests, останется прежним. Почему слева List, а не ArrayList, — частый вопрос на собеседовании.

Числа в списке: List<Integer>

Параметром типа может быть только ссылочный тип, например класс, интерфейс или запись. Примитив вроде int туда не подходит, поэтому List<int> не скомпилируется:

TEXT
error: unexpected type

Ниже javac поясняет: required: reference, found: int. Нужен ссылочный тип, а найден примитив.

Для чисел берут класс-обёртку из темы об обёртках: List<Integer>. Остальное делает автоупаковка. При add она кладёт монету-int в конверт-Integer, а когда нужен int, достаёт её обратно:

JAVA
List<Integer> nums = new ArrayList<>();
nums.add(5);
nums.add(7);
int sum = nums.get(0) + nums.get(1);
System.out.println(sum); // 12

nums.add(5) упаковывает 5 в Integer. В строке с суммой get возвращает два Integer, и для сложения они распаковываются. С другими примитивами так же: List<Double>, List<Boolean>, List<Character>.

Здесь картинка с полкой неточна. На полке списка лежат не сами объекты, а ссылки на них, как в переменной из темы о классах. Поэтому один и тот же объект может лежать сразу в двух списках.

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

Импортировали только ArrayList:

JAVA
import java.util.ArrayList;

public class Main {
    public static void main(String[] args) {
        List<String> guests = new ArrayList<>(); // ✗
    }
}
TEXT
error: cannot find symbol

Программа не компилируется. Имя ArrayList компилятор знает, а List — нет, и ниже сообщения javac называет его: symbol: class List. Строка import java.util.ArrayList; подключает только имя ArrayList, поэтому здесь нужна ещё import java.util.List;.

Главное

  • Коллекции из пакета java.util хранят группу объектов и растут сами; import java.util.ArrayList; подключает только ArrayList, а List — отдельная строка.
  • List<String> — список строк: в угловых скобках параметр типа, и другой тип компилятор в список не пустит.
  • Переменную объявляют интерфейсом List, а объект создают через new ArrayList<>(); пустые скобки <> берут тип слева.
  • Параметр типа — только ссылочный тип: List<int> не компилируется, а числа хранят в List<Integer>.