Jak skopiować tablicę w Javie

1. Przegląd

W tym krótkim artykule omówimy różne metody kopiowania tablic w Javie. Kopiowanie tablicowe może wydawać się trywialnym zadaniem, ale może spowodować nieoczekiwane wyniki i zachowanie programu, jeśli nie zostanie wykonane ostrożnie.

2. Klasa systemu

Zacznijmy od podstawowej biblioteki Java - System.arrayCopy () ; Powoduje to skopiowanie tablicy z tablicy źródłowej do tablicy docelowej, rozpoczynając akcję kopiowania od pozycji źródłowej do pozycji docelowej do określonej długości.

Liczba elementów skopiowanych do tablicy docelowej jest równa określonej długości. Zapewnia łatwy sposób kopiowania podsekwencji tablicy do innej.

Jeśli którykolwiek z argumentów tablicy ma wartość null, zgłasza wyjątek NullPointerException, a jeśli którykolwiek z argumentów liczb całkowitych jest ujemny lub poza zakresem, zgłasza IndexOutOfBoundException .

Spójrzmy na przykład kopiowania pełnej tablicy do innej przy użyciu klasy java.util.System :

int[] array = {23, 43, 55}; int[] copiedArray = new int[3]; System.arraycopy(array, 0, copiedArray, 0, 3);

Argumentami, które przyjmuje ta metoda są; tablica źródłowa, pozycja początkowa do skopiowania z tablicy źródłowej, tablica docelowa, pozycja początkowa w tablicy docelowej oraz liczba elementów do skopiowania.

Spójrzmy na inny przykład, który pokazuje kopiowanie sekwencji podrzędnej z tablicy źródłowej do miejsca docelowego:

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. Klasa Arrays

Macierze klasy oferuje również wiele metod przeciążonych skopiować tablicę do innego. Wewnętrznie wykorzystuje to samo podejście, które zapewnia klasa System , które widzieliśmy wcześniej. Udostępnia głównie dwie metody, copyOf (…) i copyRangeOf (…) .

Przyjrzyjmy się najpierw copyOf :

int[] array = {23, 43, 55, 12}; int newLength = array.length; int[] copiedArray = Arrays.copyOf(array, newLength); 

Należy zauważyć, że klasa Arrays używa funkcji Math.min (…) do wybrania minimalnej długości tablicy źródłowej oraz wartości nowego parametru długości do określenia rozmiaru wynikowej tablicy.

Arrays.copyOfRange () pobiera 2 parametry, „ od” i „ do” oprócz parametru tablicy źródłowej. Wynikowa tablica zawiera indeks „ od”, ale indeks „do” jest wykluczony. Zobaczmy przykład:

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

Obie te metody wykonują płytką kopię obiektów, jeśli są stosowane do tablicy nieprymitywnych typów obiektów. Zobaczmy przykładowy przypadek testowy:

Employee[] copiedArray = Arrays.copyOf(employees, employees.length); employees[0].setName(employees[0].getName() + "_Changed"); assertArrayEquals(copiedArray, array);

Ponieważ wynikiem jest płytka kopia - zmiana nazwiska pracownika elementu oryginalnej tablicy spowodowała zmianę w tablicy copy.

I tak - jeśli chcemy zrobić głęboką kopię typów nieprymitywnych - możemy przejść do innych opcji opisanych w kolejnych sekcjach.

4. Array Copy With Object.clone ()

Object.clone () jest dziedziczona z klasy Object w tablicy.

Najpierw skopiujmy tablicę typów pierwotnych za pomocą metody clone:

int[] array = {23, 43, 55, 12}; int[] copiedArray = array.clone(); 

I dowód, że to działa:

assertArrayEquals(copiedArray, array); array[0] = 9; assertTrue(copiedArray[0] != array[0]);

Powyższy przykład pokazuje, że mają tę samą zawartość po klonowaniu, ale zawierają różne odniesienia, więc jakakolwiek zmiana w którymkolwiek z nich nie wpłynie na drugą.

Z drugiej strony, jeśli sklonujemy tablicę typów innych niż pierwotne przy użyciu tej samej metody, wyniki będą inne.

Tworzy płytką kopię elementów tablicy typów innych niż pierwotne, nawet jeśli klasa zamkniętego obiektu implementuje interfejs Cloneable i zastępuje metodę clone () z klasy Object .

Spójrzmy na przykład:

public class Address implements Cloneable { // ... @Override protected Object clone() throws CloneNotSupportedException { super.clone(); Address address = new Address(); address.setCity(this.city); return address; } } 

Możemy przetestować naszą implementację, tworząc nową tablicę adresów i wywołując naszą metodę clone () :

Address[] addresses = createAddressArray(); Address[] copiedArray = addresses.clone(); addresses[0].setCity(addresses[0].getCity() + "_Changed"); 
assertArrayEquals(copiedArray, addresses);

Ten przykład pokazuje, że każda zmiana w oryginalnej lub skopiowanej tablicy spowodowałaby zmianę w drugiej, nawet jeśli zamknięte obiekty są klonowalne .

5. Korzystanie z Stream API

Okazuje się, że możemy użyć Stream API do kopiowania tablic. Spójrzmy na przykład:

String[] strArray = {"orange", "red", "green'"}; String[] copiedArray = Arrays.stream(strArray).toArray(String[]::new); 

W przypadku typów niebędących prymitywami wykona również płytką kopię obiektów. Aby dowiedzieć się więcej o strumieniach Java 8 , możesz zacząć tutaj.

6. Biblioteki zewnętrzne

Apache Commons 3 oferuje klasę narzędziową o nazwie SerializationUtils, która udostępnia metodę clone (…) . Jest to bardzo przydatne, jeśli musimy zrobić głęboką kopię tablicy typów innych niż pierwotne. Można go pobrać stąd, a jego zależność Maven to:

 org.apache.commons commons-lang3 3.5  

Rzućmy okiem na przypadek testowy:

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

Ta klasa wymaga, aby każdy obiekt implementował interfejs Serializable . Pod względem wydajności jest wolniejszy niż metody klonowania napisane ręcznie dla każdego z obiektów w naszym grafie obiektów do skopiowania.

7. Wnioski

W tym samouczku przyjrzeliśmy się różnym opcjom kopiowania tablicy w Javie.

Zastosowana metoda zależy głównie od dokładnego scenariusza. Dopóki używamy tablicy typu pierwotnego, możemy używać dowolnej metody oferowanej przez klasy System i Arrays . Nie powinno być żadnej różnicy w wydajności.

W przypadku typów innych niż pierwotne, jeśli potrzebujemy wykonać głęboką kopię tablicy, możemy użyć SerializationUtils lub jawnie dodać metody klonowania do naszych klas.

I jak zawsze przykłady przedstawione w tym artykule są dostępne na GitHub.