Як скопіювати масив на 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 offers a utility class called SerializationUtils that provides a clone(…) method. It is very useful if we need to do a deep copy of an array of non-primitive types. It can be downloaded from here and its Maven dependency is:

 org.apache.commons commons-lang3 3.5  

Let's have a look at a test case:

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

This class requires that each object should implement the Serializable interface. In terms of performance, it is slower than the clone methods written manually for each of the objects in our object graph to copy.

7. Conclusion

In this tutorial, we had a look at the various options to copy an array in Java.

Використовуваний метод в основному залежить від точного сценарію. Поки ми використовуємо масив примітивного типу, ми можемо використовувати будь-який із методів, запропонованих класами System та Arrays . Ніякої різниці у продуктивності бути не повинно.

Для непримітивних типів, якщо нам потрібно зробити глибоку копію масиву, ми можемо використовувати SerializationUtils або явно додати методи клонування до наших класів.

І як завжди, приклади, показані в цій статті, доступні на GitHub.