Копирование массивов в Java
Копирование массивов в Java: System.arraycopy, Arrays.copyOf, clone и потоки.
Присваивание одной переменной-массива другой не копирует данные — оно просто создаёт два имени, указывающих на один и тот же массив:
int[] a = {1, 2, 3};
int[] b = a;
b[0] = 99;
System.out.println(a[0]); // 99 — same arrayЧтобы получить независимую копию, нужно явно её запросить. Java предлагает несколько способов, каждый из которых подходит для своей ситуации: полное дублирование, изменение размера при копировании, извлечение среза, копирование между двумя существующими массивами. Большинство из них находятся в утилитарном классе Arrays, поэтому в примерах ниже предполагается наличие import java.util.Arrays;.
Arrays.copyOf — дублирование или изменение размера
Принимает исходный массив и целевую длину. Если новая длина совпадает, вы получаете чистую копию. Если она больше, дополнительные ячейки заполняются значениями по умолчанию (0, false, null); если меньше — хвост отбрасывается.
import java.util.Arrays;
int[] data = {1, 2, 3, 4, 5};
int[] same = Arrays.copyOf(data, data.length); // {1, 2, 3, 4, 5}
int[] grown = Arrays.copyOf(data, 8); // {1, 2, 3, 4, 5, 0, 0, 0}
int[] shrunk = Arrays.copyOf(data, 3); // {1, 2, 3}Именно так «увеличивают» массив фиксированного размера — выделяют более крупный и копируют в него данные.
Arrays.copyOfRange — срез
Копирует полуоткрытый диапазон [from, to):
int[] data = {1, 2, 3, 4, 5};
int[] middle = Arrays.copyOfRange(data, 1, 4); // {2, 3, 4}Значение to может превышать длину исходного массива — дополнительные ячейки заполняются значениями по умолчанию. Таким образом, срез и дополнение можно выполнить за один вызов.
System.arraycopy — копирование в существующий массив
Это примитив самого низкого уровня: копирует диапазон одного массива в диапазон другого. Не выделяет новую память, не возвращает значение — оба массива должны уже существовать.
int[] src = {10, 20, 30, 40, 50};
int[] dst = new int[8];
System.arraycopy(src, 1, dst, 3, 3);
// dst is now {0, 0, 0, 20, 30, 40, 0, 0}Аргументы: (src, srcStart, dst, dstStart, length). Используйте этот метод, когда нужно поместить данные по заданному смещению или переместить элементы внутри одного массива — например, System.arraycopy(arr, i, arr, i + 1, length - i - 1) сдвигает срез вправо на один, освобождая место для вставки. Перекрывающиеся диапазоны внутри одного массива поддерживаются и обрабатываются корректно.
clone() — быстрое полное копирование
Каждый массив имеет метод clone(), который возвращает новый массив той же длины с теми же содержимым:
int[] data = {1, 2, 3};
int[] copy = data.clone();Для одномерных массивов clone() — самый короткий и наглядный способ создать дубликат. Тип возвращаемого значения совпадает с исходным — приведение типов не требуется.
Для многомерных массивов clone() является поверхностным: он копирует внешний массив, но строки по-прежнему остаются исходными ссылками:
int[][] grid = {{1, 2}, {3, 4}};
int[][] shallow = grid.clone();
shallow[0][0] = 99;
System.out.println(grid[0][0]); // 99 — same inner rowЧтобы выполнить глубокое копирование двумерного массива, нужно клонировать каждую строку отдельно:
int[][] deep = new int[grid.length][];
for (int r = 0; r < grid.length; r++) deep[r] = grid[r].clone();Вариант с потоком: Arrays.stream(grid).map(int[]::clone).toArray(int[][]::new).
Потоки
Для массивов объектов Arrays.stream(arr).toArray(T[]::new) создаёт копию:
String[] names = {"Ada", "Linus", "Grace"};
String[] copy = Arrays.stream(names).toArray(String[]::new);Для массивов примитивов используйте специализированный поток:
import java.util.stream.IntStream;
int[] data = {1, 2, 3, 4, 5};
int[] copy = IntStream.of(data).toArray();Потоки требуют больше ресурсов, чем copyOf или clone(), и применяются главным образом для случаев преобразования — .map(...) или .filter(...) в середине цепочки — а не для простого дублирования.
Ссылочные и глубокие копии
Для примитивов каждая копия полностью независима — дублируются сами значения. Для массивов объектов все рассмотренные методы копирования являются поверхностными: они дублируют массив ссылок, но каждая ссылка по-прежнему указывает на тот же объект, что и в оригинале.
StringBuilder[] src = { new StringBuilder("hi") };
StringBuilder[] copy = src.clone();
copy[0].append(" there");
System.out.println(src[0]); // "hi there" — same StringBuilderОбычно это не проблема — Stringами, Integerами и любыми неизменяемыми объектами можно безопасно делиться. Если элементы изменяемы и нужны независимые объекты, придётся копировать их вручную:
StringBuilder[] deep = new StringBuilder[src.length];
for (int i = 0; i < src.length; i++) deep[i] = new StringBuilder(src[i]);Выбор метода копирования
| Что нужно | Использовать |
|---|---|
| Точный дубликат одномерного массива | arr.clone() или Arrays.copyOf(arr, arr.length) |
| Изменение размера при копировании (увеличить/уменьшить) | Arrays.copyOf(arr, newLength) |
| Срез — копирование части массива | Arrays.copyOfRange(arr, from, to) |
| Копирование в ячейку существующего массива | System.arraycopy(...) |
| Глубокое копирование двумерного массива | клонировать каждую строку в цикле |
| Копирование с преобразованием | потоки (map, filter, затем toArray) |
Рабочий пример
Что дальше
Теперь вы умеете создавать, обходить, искать, сортировать и копировать массивы — это полный набор инструментов для работы с контейнерами фиксированного размера в Java. Следующая часть посвящена методам: как упаковывать код в именованные многократно используемые единицы, принимающие параметры и возвращающие значения.