Источник: Baeldung
В этом руководстве подробно рассмотрены возможности служебного класса java.util.Arrays, который является частью языка Java, начиная с версии Java 1.2.
При необходимости сравнить, скопировать или отсортировать массивы, многие разработчики прибегают к таким операциям, как написание циклов, операторов if и другой логики, необходимой для такой обработки. В большинстве случаев это ошибочное решение, поскольку такие операции дублируют поведение, доступное в служебном классе java.util.Arrays. Давайте посмотрим, как использовать класс Arrays для обработки повседневных операций с массивами.

1. Создание массивов
Для начала рассмотрим некоторые способы создания массивов: copyOf, copyOfRange и fill.1.1. copyOf и copyOfRange
Чтобы использовать copyOfRange, нам нужен наш исходный массив, начальный индекс (включительно) и конечный индекс (исключительно), которые мы хотим скопировать:
String[] intro = new String[] { "once", "upon", "a", "time" };
String[] abridgement = Arrays.copyOfRange(storyIntro, 0, 3);
assertArrayEquals(new String[] { "once", "upon", "a" }, abridgement);
assertFalse(Arrays.equals(intro, abridgement));
А чтобы использовать copyOf, нам нужно взять intro и размер целевого массива. Так мы получим новый массив следующей длины:
String[] revised = Arrays.copyOf(intro, 3);
String[] expanded = Arrays.copyOf(intro, 5);
assertArrayEquals(Arrays.copyOfRange(intro, 0, 3), revised);
assertNull(expanded[4]);
Обратите внимание, что copyOf дополняет массив нулями, если наш целевой размер больше исходного размера.
1.2. fill
Еще один способ, которым мы можем создать массив фиксированной длины, это fill (заполнение). Его полезно применять, когда нам нужен массив, в котором все элементы одинаковы:
String[] stutter = new String[3];
Arrays.fill(stutter, "once");
assertTrue(Stream.of(stutter)
.allMatch(el -> "once".equals(el));
Если вам нужно создать массив, в котором элементы различаются, то попробуйте команду setAll.
Обратите внимание, что нам нужно заранее создать экземпляр массива — в отличие от чего-то вроде String[] fill = Arrays.fill("once" , 3); – поскольку эта функция была введена до того, как в языке стали доступны дженерики.
2. Сравнение
Теперь перейдем к методам сравнения массивов.2.1 equals and deepEquals
Для простого сравнения массивов по размеру и содержимому можно использовать equals. Но если мы добавим null в качестве одного из элементов, то проверка содержимого завершится неудачно:
assertTrue(
Arrays.equals(new String[] { "once", "upon", "a", "time" }, intro));
assertFalse(
Arrays.equals(new String[] { "once", "upon", "a", null }, intro));
Когда у нас есть вложенные или многомерные массивы, мы можем использовать deepEquals не только для проверки элементов верхнего уровня, но и для рекурсивного выполнения проверки:
Object[] story = new Object[]
{ intro, new String[] { "chapter one", "chapter two" }, end };
Object[] copy = new Object[]
{ intro, new String[] { "chapter one", "chapter two" }, end };
assertTrue(Arrays.deepEquals(story, copy));
assertFalse(Arrays.equals(story, copy));
Обратите внимание, как deepEquals выполняется успешно, а equals терпит неудачу. Это связано с тем, что deepEquals в конечном итоге вызывает сам себя каждый раз, когда сталкивается с массивом, а equals просто сравнивает ссылки на подмассивы. Учтите, что довольно опасно обращаться к массиву с самоссылкой!
2.2. hashCode and deepHashCode
Реализация hashCode даст нам другую часть контракта equals/hashCode, которая рекомендуется для объектов Java. Мы используем hashCode для вычисления целого числа (integer) на основе содержимого массива:
Object[] looping = new Object[]{ intro, intro };
int hashBefore = Arrays.hashCode(looping);
int deepHashBefore = Arrays.deepHashCode(looping);
Теперь мы присваиваем элементу исходного массива значение null и повторно вычисляем хэш-значения:
intro[3] = null;
int hashAfter = Arrays.hashCode(looping);
Что касается deepHashCode, то он проверяет вложенные массивы на соответствие количества элементов и содержимого. Вот так мы выполняем пересчет с помощью deepHashCode:
int deepHashAfter = Arrays.deepHashCode(looping);
Теперь мы можем увидеть разницу между двумя методами:
assertEquals(hashAfter, hashBefore);
assertNotEquals(deepHashAfter, deepHashBefore);
deepHashCode — это основной способ расчета, используемый при работе в массивах с такими структурами данных, как HashMap и HashSet.
3. Сортировка и поиск
Далее мы рассмотрим сортировку и поиск массивов.3.1. Сортировка
Если наши элементы либо примитивны, либо реализуют Comparable, то мы можем использовать sort для выполнения встроенной сортировки:
String[] sorted = Arrays.copyOf(intro, 4);
Arrays.sort(sorted);
assertArrayEquals(
new String[]{ "a", "once", "time", "upon" },
sorted);
Заметьте, что sort изменил исходную ссылку, поэтому здесь мы делаем копию.
Обратите внимание, что sort будет использовать разный алгоритм для разных типов элементов массива. Типы-примитивы используют быструю сортировку с двумя опорными точками, а типы объектов используют Timsort. Оба имеют средний результат O(n log(n)) для случайно отсортированного массива.
Начиная с Java 8, для параллельной сортировки-слияния нам доступен parallelSort. Он предлагает параллельный метод сортировки с использованием нескольких задач Arrays.sort.
3.2. binarySearch
Поиск в несортированном массиве линейный, но если у нас отсортированный массив, то мы можем сделать это в O(log n) с помощью binarySearch:
int exact = Arrays.binarySearch(sorted, "time");
int caseInsensitive = Arrays.binarySearch(sorted, "TiMe", String::compareToIgnoreCase);
assertEquals("time", sorted[exact]);
assertEquals(2, exact);
assertEquals(exact, caseInsensitive);
Если мы не предоставляем Comparator в качестве третьего параметра, то binarySearch рассчитывает, что тип нашего элемента относится к типу Comparable.
И снова обратите внимание, что если наш массив не отсортирован, то binarySearch не будет работать так, как мы ожидаем!
4. Потоковые методы
Как мы уже знаем, Arrays были обновлены в Java 8 и теперь включают методы, использующие Stream API, такие как parallelSort (упомянутый выше), stream и setAll.4.1. stream
stream дает нам для нашего массива полный доступ к Stream API:
Assert.assertEquals(Arrays.stream(intro).count(), 4);
exception.expect(ArrayIndexOutOfBoundsException.class);
Arrays.stream(intro, 2, 1).count();
Мы можем предоставить инклюзивные и эксклюзивные индексы для потока, однако мы должны ожидать появления ошибки ArrayIndexOutOfBoundsException, если индексы out of order (не в порядке), negative (отрицательные) или out of range (вне диапазона).
5. Преобразование массивов
Используя toString, asList и setAll, мы имеем несколько способов преобразования массивов.5.1. toString и DeepToString
Отличный способ получить удобочитаемую версию исходного массива — использовать toString:
assertEquals("[once, upon, a, time]", Arrays.toString(storyIntro));
Для печати содержимого вложенных массивов мы снова должны использовать “глубокую” версию — DeepToString:
assertEquals(
"[[once, upon, a, time], [chapter one, chapter two], [the, end]]",
Arrays.deepToString(story));
5.2. asList
Наиболее удобным из всех методов массивов для нас является asList. Он дает нам простой способ превратить массив в список (List):
List<String> rets = Arrays.asList(storyIntro);
assertTrue(rets.contains("upon"));
assertTrue(rets.contains("time"));
assertEquals(rets.size(), 4);
Однако возвращаемый список будет фиксированной длины, поэтому мы не сможем добавлять или удалять элементы.
Обратите также внимание на то, что, как ни странно, java.util.Arrays имеет свой собственный подкласс ArrayList, который возвращает asList. Это может выглядеть очень обманчиво при отладке!
5.3. setAll
С помощью setAll мы можем установить все элементы массива с функциональным интерфейсом. Реализация генератора в качестве параметра принимает позиционный индекс:
String[] longAgo = new String[4];
Arrays.setAll(longAgo, i -> this.getWord(i));
assertArrayEquals(longAgo, new String[]{"a","long","time","ago"});
Конечно же, обработка исключений — одна из самых рискованных частей использования лямбда-выражений. Поэтому помните, что здесь, если лямбда выдает исключение, Java не определяет конечное состояние массива.
6. Parallel Prefix
Еще один новый метод в Arrays, появившийся после Java 8, — это parallelPrefix. Используя parallelPrefix, мы можем работать с каждым элементом входного массива кумулятивно.6.1. parallelPrefix
Если оператор выполняет кумулятивное сложение (одного числа за другим), как в следующем примере, [1, 2, 3, 4], то это даст результат [1, 3, 6, 10]:
int[] arr = new int[] { 1, 2, 3, 4};
Arrays.parallelPrefix(arr, (left, right) -> left + right);
assertThat(arr, is(new int[] { 1, 3, 6, 10}));
Также мы можем указать поддиапазон для операции:
int[] arri = new int[] { 1, 2, 3, 4, 5 };
Arrays.parallelPrefix(arri, 1, 4, (left, right) -> left + right);
assertThat(arri, is(new int[] { 1, 2, 5, 9, 5 }));
Заметьте, что метод выполняется параллельно, поэтому кумулятивная операция должна быть без побочных эффектов и ассоциативной.
Для неассоциативной функции:
int nonassociativeFunc(int left, int right) {
return left + right*left;
}
Использование parallelPrefix приведет к противоречивым результатам:
@Test
public void whenPrefixNonAssociative_thenError() {
boolean consistent = true;
Random r = new Random();
for (int k = 0; k < 100_000; k++) {
int[] arrA = r.ints(100, 1, 5).toArray();
int[] arrB = Arrays.copyOf(arrA, arrA.length);
Arrays.parallelPrefix(arrA, this::nonassociativeFunc);
for (int i = 1; i < arrB.length; i++) {
arrB[i] = nonassociativeFunc(arrB[i - 1], arrB[i]);
}
consistent = Arrays.equals(arrA, arrB);
if(!consistent) break;
}
assertFalse(consistent);
}
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ