Преобразование массива в 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.
Комментарии