Odwracanie kolejności elementów tabeli
Potrzebowałem utworzyć tabelę, zawierającą elementy tabeli źródłowej, lecz w odwrotnej kolejności. Zadanie jest dziecinnie proste, jednak chciałem się upewnić, czy nie ma już specjalnie dla tego celu przygotowanej metody – na przykład w klasie java.util.Arrays. Wówczas natrafiłem na ciekawe rozwiązanie problemu:
public static Object[] reverse(Object[] arr) {
List<Object> list = Arrays.asList(arr);
Collections.reverse(list);
return list.toArray();
}
Oczywiście! Przecież metoda, której szukam, została zaimplementowana dla kolekcji.
Na wszelki wypadek postanowiłem przetestować znaleziony algorytm:
@Test
public void testReverse() {
Object[] objects = new Object[] {
1, 2, 3, 4, 5, 6, 7, 8, 9, 10
};
Object[] reversed = ArrayHandle.reverse(objects);
assertEquals(reversed[0], (Object) 10,
"First element of reversed array");
}
Tak! Działa!
Jeszcze tylko upewnię się, że tabela źródłowa pozostaje niezmieniona:
@Test
public void testReverse() {
// …
assertEquals(objects[0], (Object) 1,
"First element of original array");
}
Uruchamiam testy i… otrzymuję błąd:
java.lang.AssertionError: First element of original array expected:<1> but was:<10>
Co się stało?
Przeanalizujmy metodę reverse.
Na samym początku tworzony jest interfejs java.util.List dla tablicy źródłowej. Zauważmy przy tym, że lista zwrócona przez Arrays.asList() odwołuje się bezpośrednio do wskazanej tablicy – nie kopiuje jej elementów.
Dzięki metodzie Collections.reverse() odwrócona zostaje kolejność elementów listy, czyli de facto elementów tablicy źródłowej.
Na samym końcu, przy użyciu metody List.toArray() tworzona jest nowa tablica, która zostaje następnie zwrócona jako wynik działania metody.
Czyli metoda reverse zmienia kolejność elementów wewnątrz tablicy podanej jako parametr na odwrotną, po czym zwraca kopię tej tablicy? Przypuszczam (albo przynajmniej mam nadzieję), że nie taka była intencja autora. Jeżeli widzę metodę o sygnaturze Object[] reverse(Object[]), to spodziewam się, że parametr pozostanie niezmieniony (już lepiej by było, gdyby ta metoda nie zwracała nic).
Postanowiłem nieco ulepszyć algorytm – tak aby kopia tablicy tworzona była przed zmianą kolejności elementów:
public static <T> T[] reverse(T[] arr) {
T[] reversed = arr.clone();
List<T> list = Arrays.asList(reversed);
Collections.reverse(list);
return reversed;
}
W ten sposób kolejność elementów tablicy wejściowej pozostaje bez zmian; ponadto unikam tworzenia nadmiarowej kopii.
Istnieje jeszcze jeden atut nowej implementacji – jest to metoda generyczna. Aby odwrócić kolejność elementów tablicy liczb całkowitych, nie muszę już korzystać z rzutowania:
Integer[] reversed = (Integer[]) ArrayHandle.reverse(integers);
Teraz mogę zwyczajnie wywołać tę metodę dla tablicy Integer[], jak w poniższej metodzie testowej:
@Test
public void testReverseForIntegers() {
Integer[] integers = new Integer[] {
1, 2, 3, 4, 5, 6, 7, 8, 9, 10
};
Integer[] reversed = ArrayHandle.reverse(integers);
assertEquals(reversed[0], (Integer) 10,
"First element of reversed array");
assertEquals(integers[0], (Integer) 1,
"First element of original array");
}
Oczywiście można ograniczyć odpowiedzialność metody reverse do zmiany kolejności elementów tablicy in situ:
public static <T> void reverse(T[] arr) {
List<T> list = Arrays.asList(arr);
Collections.reverse(list);
}




W Commons Lang jest ArrayUtils a w Commons Collections – CollectionUtils. Obie mają metody do odwracania tablic (C&P, czemu?), tyle że działające in-place.
Na przedstawiony problem natrafiłem już przygotowując się do SCJP. Otóż w pytaniu była tablica String’ów z której była tworzona List’a za pomocą Arrays.asList(). Następnie był modyfikowany element tablicy, co pociągało za sobą modyfikację elementu listy i odwrotnie: modyfikacja elementu listy pociągała za sobą modyfikację w tablicy.
Warto o tym zjawisku pamiętać.