Stream API ·
‹ Предыдущий Следующий ›
⏱ 5 минут чтения Обновлено: 2026-09-16

Преобразование массива в List и обратно

Разработчик пишет List<Integer> list = Arrays.asList(numbers); для массива int[] numbers = {1, 2, 3} — и код даже не компилируется, а если подправить тип, то list.size() внезапно возвращает 1 вместо 3. Массив примитивов целиком стал одним элементом списка. Это самая частая ошибка в теме, с которой начинают почти все.

Преобразование массива в коллекцию — это перенос элементов массива фиксированной длины (int[], Integer[]) в объект List<Integer> и обратно. Массив хранит элементы в непрерывном блоке заданного размера, а List — интерфейс с удобными методами добавления, удаления и фильтрации. Массив примитивов нельзя положить в коллекцию напрямую: коллекции работают только с объектами, поэтому int приходится упаковывать в Integer.

Ниже — рабочие способы преобразования в обе стороны: через Stream API, обычным циклом, а также разбор случаев Integer[], ArrayList и ошибки с Arrays.asList().

Массив int[] в List<Integer>

Массив примитивов превращается в список за три шага: Arrays.stream() создаёт IntStream, boxed() упаковывает каждый int в Integer, а терминальная операция собирает результат в коллекцию.

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

int[] numbers = {1, 2, 3, 4, 5};

List<Integer> list = Arrays.stream(numbers) // IntStream
                           .boxed()         // Stream<Integer>
                           .collect(Collectors.toList());

System.out.println(list); // [1, 2, 3, 4, 5]

Начиная с Java 16 у потока есть более короткий метод toList():

List<Integer> list = Arrays.stream(numbers).boxed().toList();

Важно

Список из Stream.toList() неизменяемый: вызов add() или set() выбросит UnsupportedOperationException. Если список нужно потом дополнять, используйте collect(Collectors.toList()) или явно попросите ArrayList.

Как получить именно ArrayList

List — это интерфейс, и конкретная реализация под ним не гарантируется. Если по сигнатуре метода нужен именно ArrayList<Integer>, попросите его явно:

import java.util.ArrayList;

// Вариант 1: через коллектор
ArrayList<Integer> arrayList = Arrays.stream(numbers)
        .boxed()
        .collect(Collectors.toCollection(ArrayList::new));

// Вариант 2: обернуть готовый список
ArrayList<Integer> copy = new ArrayList<>(list);

arrayList.add(6); // работает, размер не фиксирован
System.out.println(arrayList); // [1, 2, 3, 4, 5, 6]

List<Integer> обратно в массив int[]

Обратное преобразование зеркально: mapToInt() распаковывает Integer в int, а toArray() собирает примитивный массив.

List<Integer> list = List.of(10, 20, 30, 40);

int[] array = list.stream()
                  .mapToInt(Integer::intValue)
                  .toArray();

System.out.println(Arrays.toString(array)); // [10, 20, 30, 40]

Если в списке может оказаться null, mapToInt(Integer::intValue) упадёт с NullPointerException при распаковке — отфильтруйте значения заранее: .filter(Objects::nonNull).

Integer[] и int[] — это разные вещи

int[] — массив примитивов, Integer[] — массив объектов-обёрток. Для коллекций удобнее второй, для вычислений и памяти — первый. Преобразование между ними тоже делается через поток:

// int[] -> Integer[]
int[] primitives = {1, 2, 3};
Integer[] boxed = Arrays.stream(primitives)
                        .boxed()
                        .toArray(Integer[]::new);

// Integer[] -> int[]
Integer[] source = {4, 5, 6};
int[] unboxed = Arrays.stream(source)
                      .mapToInt(Integer::intValue)
                      .toArray();

// Integer[] -> List<Integer> (изменяемый список)
List<Integer> list = new ArrayList<>(Arrays.asList(source));

// List<Integer> -> Integer[]
Integer[] back = list.toArray(new Integer[0]);

Обратите внимание: Arrays.asList(source) для Integer[] действительно даёт List<Integer> из трёх элементов — потому что это массив объектов. С int[] тот же код ведёт себя иначе.

Arrays.asList() с массивом примитивов: где ломается

Arrays.asList() принимает varargs-параметр T... a, а T обязан быть ссылочным типом. Массив int[] сам является объектом, поэтому компилятор считает его одним аргументом и возвращает List<int[]> размером 1.

int[] numbers = {1, 2, 3};

// Неправильно: список из ОДНОГО элемента — самого массива
List<int[]> wrong = Arrays.asList(numbers);
System.out.println(wrong.size()); // 1

// Правильно: сначала распаковываем поток
List<Integer> right = Arrays.stream(numbers).boxed().toList();
System.out.println(right.size()); // 3

Ещё один неочевидный момент

Даже для Integer[] результат Arrays.asList() — список фиксированного размера, связанный с исходным массивом. Методы add() и remove() бросят UnsupportedOperationException, а set() изменит сам массив. Нужен полноценный изменяемый список — оберните: new ArrayList<>(Arrays.asList(source)).

Без Stream API: обычный цикл и перебор массива

Цикл никуда не делся: он нагляднее в учебных задачах, не создаёт промежуточных объектов потока и остаётся единственным вариантом, если по ходу преобразования нужно менять индексы или прерывать обход.

int[] arr = {1, 2, 3};

// Массив -> List
List<Integer> list = new ArrayList<>();
for (int value : arr) {          // перебор массива: for-each
    list.add(value);             // автоупаковка int -> Integer
}

// List -> массив
int[] backToArray = new int[list.size()];
for (int i = 0; i < list.size(); i++) {  // перебор по индексу
    backToArray[i] = list.get(i);
}

Когда данные уже в списке, перебирать его можно тем же for-each или методом forEach():

for (int value : arr) {
    System.out.println(value);
}

list.forEach(System.out::println);

Почему нельзя написать List<int>

Дженерики в Java работают только со ссылочными типами: во время выполнения параметр типа стирается до Object, а примитив int объектом не является. Поэтому List<int> не компилируется, и используется обёртка — List<Integer>. Методы boxed() и mapToInt() как раз и переводят данные между этими двумя мирами.

Шпаргалка: какой способ выбрать

Преобразование Код Примечание
int[] → List<Integer> Arrays.stream(arr).boxed().collect(Collectors.toList()) Изменяемый список; .toList() (Java 16+) вернёт неизменяемый
List<Integer> → int[] list.stream().mapToInt(Integer::intValue).toArray() null в списке вызовет NPE
int[] → ArrayList<Integer> ...boxed().collect(Collectors.toCollection(ArrayList::new)) Когда нужна конкретная реализация
int[] → Integer[] Arrays.stream(arr).boxed().toArray(Integer[]::new) Массив обёрток, а не список
Integer[] → List<Integer> new ArrayList<>(Arrays.asList(arr)) Без обёртки список будет фиксированного размера
List<Integer> → Integer[] list.toArray(new Integer[0]) Передавать массив нулевой длины — норма

Stream API даёт лаконичный и читаемый код в одну строку; цикл выигрывает, когда нужен полный контроль над обходом. Главное — помнить, что Arrays.asList() с массивом примитивов задачу не решает.

Часто задаваемые вопросы

Почему Arrays.asList(intArray) возвращает список из одного элемента?

Метод объявлен как Arrays.asList(T... a), а параметр типа T может быть только ссылочным. Массив int[] — это объект, поэтому он воспринимается как один аргумент и результат имеет тип List<int[]> размером 1. Для получения List<Integer> нужен Arrays.stream(arr).boxed().

Как получить именно ArrayList, а не просто List?

Используйте collect(Collectors.toCollection(ArrayList::new)) или оберните готовую коллекцию: new ArrayList<>(list). Оба варианта дают изменяемый ArrayList, в который можно добавлять и удалять элементы.

Чем Stream.toList() отличается от collect(Collectors.toList())?

Метод toList() появился в Java 16 и всегда возвращает неизменяемый список, допускающий null. Коллектор Collectors.toList() возвращает изменяемый список, обычно ArrayList, но конкретная реализация не гарантируется спецификацией.

Как преобразовать Integer[] в int[] и обратно?

Из Integer[] в int[]: Arrays.stream(boxed).mapToInt(Integer::intValue).toArray(). Обратно: Arrays.stream(primitives).boxed().toArray(Integer[]::new). Простое присваивание одного массива другому не сработает — это несовместимые типы.

Что быстрее: обычный цикл или Stream API?

На небольших массивах цикл обычно быстрее, потому что не создаёт объекты потока и не требует прогрева JIT. На практике разница заметна только в горячем коде и при миллионах элементов, поэтому выбирайте по читаемости, а при реальных требованиях к скорости измеряйте бенчмарком, например на JMH.

Комментарии

Зарегистрируйтесь или войдите, чтобы иметь возможность оставить комментарий.