Как скопировать массив в Java

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 в массиве.

Давайте сначала скопируем массив примитивных типов с помощью метода clone:

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);

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

5. Использование Stream 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:

 org.apache.commons commons-lang3 3.5  

Давайте посмотрим на тестовый пример:

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.