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.