1. Обзор
В этой быстрой статье мы обсудим различные методы копирования массивов в Java. Копирование массива может показаться тривиальной задачей, но при неаккуратном выполнении оно может привести к неожиданным результатам и поведению программы.
2. Системный
класс
Начнем с базовой библиотеки Java — System.arrayCopy()
; это копирует массив из исходного массива в целевой массив, начиная действие копирования из исходной позиции в целевую позицию до указанной длины.
Количество элементов, скопированных в целевой массив, равно указанной длине. Он обеспечивает простой способ копирования подпоследовательности массива в другую.
Если какой-либо из аргументов массива имеет значение null,
генерируется исключение NullPointerException
, а если любой из целочисленных аргументов является отрицательным или выходит за пределы допустимого диапазона, генерируется исключение IndexOutOfBoundException
.
Давайте рассмотрим пример копирования полного массива в другой с помощью класса java.util.System :
int[] array = {23, 43, 55};
int[] copiedArray = new int[3];
System.arraycopy(array, 0, copiedArray, 0, 3);
Аргументы, которые принимает этот метод: исходный массив, начальная позиция для копирования из исходного массива, целевой массив, начальная позиция в целевом массиве и количество копируемых элементов.
Давайте посмотрим на другой пример, который показывает копирование подпоследовательности из исходного массива в место назначения:
int[] array = {23, 43, 55, 12, 65, 88, 92};
int[] copiedArray = new int[3];
System.arraycopy(array, 2, copiedArray, 0, 3);
assertTrue(3 == copiedArray.length);
assertTrue(copiedArray[0] == array[2]);
assertTrue(copiedArray[1] == array[3]);
assertTrue(copiedArray[2] == array[4]);
3. Класс массивов
Класс Arrays
также предлагает несколько перегруженных методов для копирования массива в другой. Внутри он использует тот же подход, что и класс System
, который мы видели ранее. В основном он предоставляет два метода: copyOf(…)
и copyRangeOf(…)
.
Давайте сначала посмотрим на copyOf
:
int[] array = {23, 43, 55, 12};
int newLength = array.length;
int[] copiedArray = Arrays.copyOf(array, newLength);
Важно отметить, что класс Arrays
использует Math.min(…)
для выбора минимальной длины исходного массива и значение нового параметра длины для определения размера результирующего массива.
Arrays.copyOfRange()
принимает 2 параметра: « от»
и « до»
в дополнение к параметру исходного массива. Результирующий массив включает индекс " от"
, но индекс "до"
исключается. Давайте посмотрим пример:
int[] array = {23, 43, 55, 12, 65, 88, 92};
int[] copiedArray = Arrays.copyOfRange(array, 1, 4);
assertTrue(3 == copiedArray.length);
assertTrue(copiedArray[0] == array[1]);
assertTrue(copiedArray[1] == array[2]);
assertTrue(copiedArray[2] == array[3]);
Оба этих метода делают поверхностную копию объектов, если они применяются к массиву непримитивных типов объектов. Давайте посмотрим на пример тестового случая:
Employee[] copiedArray = Arrays.copyOf(employees, employees.length);
employees[0].setName(employees[0].getName() + "_Changed");
assertArrayEquals(copiedArray, array);
Поскольку результатом является поверхностная копия — изменение имени сотрудника элемента исходного массива вызвало изменение массива копии.
Итак, если мы хотим сделать глубокую копию непримитивных типов, мы можем использовать другие варианты, описанные в следующих разделах.
4. Копирование массива с помощью Object.clone()
Object.clone()
наследуется от класса Object
в массиве.
Давайте сначала скопируем массив примитивных типов, используя метод клонирования:
int[] array = {23, 43, 55, 12};
int[] copiedArray = array.clone();
И доказательство того, что это работает:
assertArrayEquals(copiedArray, array);
array[0] = 9;
assertTrue(copiedArray[0] != array[0]);
В приведенном выше примере показано, что содержимое после клонирования остается одинаковым, но они содержат разные ссылки, поэтому любое изменение в одном из них не повлияет на другое.
С другой стороны, если мы клонируем массив непримитивных типов тем же методом, то результаты будут другими.
Он создает поверхностную копию элементов массива непримитивного типа, даже если класс вложенного объекта реализует интерфейс Cloneable
и переопределяет метод clone()
из класса Object .
Давайте посмотрим на пример:
public class Address implements Cloneable {
// ...
@Override
protected Object clone() throws CloneNotSupportedException {
super.clone();
Address address = new Address();
address.setCity(this.city);
return address;
}
}
Мы можем протестировать нашу реализацию, создав новый массив адресов и вызвав наш метод clone() :
Address[] addresses = createAddressArray();
Address[] copiedArray = addresses.clone();
addresses[0].setCity(addresses[0].getCity() + "_Changed");
assertArrayEquals(copiedArray, addresses);
Этот пример показывает, что любое изменение в исходном или скопированном массиве вызовет изменение в другом массиве, даже если вложенные объекты являются Cloneable
.
5. Использование потокового
API
Оказывается, мы можем использовать Stream API и для копирования массивов. Давайте посмотрим на пример:
String[] strArray = {"orange", "red", "green'"};
String[] copiedArray = Arrays.stream(strArray).toArray(String[]::new);
Для непримитивных типов также будет выполняться поверхностная копия объектов. Чтобы узнать больше о Java 8 Streams
, вы можете начать здесь .
6. Внешние библиотеки
Apache Commons 3
предлагает служебный класс SerializationUtils
, который предоставляет метод clone(…) .
Это очень полезно, если нам нужно сделать глубокую копию массива непримитивных типов. Его можно скачать отсюда, и его зависимость от Maven:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
Давайте посмотрим на тестовый пример:
public class Employee implements Serializable {
// fields
// standard getters and setters
}
Employee[] employees = createEmployeesArray();
Employee[] copiedArray = SerializationUtils.clone(employees);
employees[0].setName(employees[0].getName() + "_Changed");
assertFalse(
copiedArray[0].getName().equals(employees[0].getName()));
Этот класс требует, чтобы каждый объект реализовывал интерфейс Serializable .
С точки зрения производительности, это медленнее, чем методы клонирования, написанные вручную для каждого из объектов в нашем графе объектов для копирования.
7. Заключение
В этом руководстве мы рассмотрели различные варианты копирования массива в Java.
Используемый метод в основном зависит от конкретного сценария. Пока мы используем массив примитивного типа, мы можем использовать любой из методов, предлагаемых классами System
и Arrays .
Разницы в производительности быть не должно.
Для непримитивных типов, если нам нужно сделать глубокую копию массива, мы можем либо использовать SerializationUtils
, либо явно добавить методы клонирования в наши классы.
И, как всегда, примеры, показанные в этой статье, доступны на GitHub .