Операции с массивами в Java

Операции с массивами в Java

1. обзор

Любой разработчик Java знает, что создание чистого и эффективного решения при работе с операциями с массивами не всегда легко. Тем не менее, они являются центральным элементом экосистемы Java, и нам придется иметь дело с ними несколько раз.

По этой причине хорошо иметь «шпаргалку» - сводку наиболее распространенных процедур, которая поможет нам быстро решить головоломку. Этот учебник пригодится в таких ситуациях.

2. Массивы и вспомогательные классы

Прежде чем продолжить, полезно понять, что такое массив в Java и как его использовать. Если вы впервые работаете с ним на Java, рекомендуем взглянуть наthis previous post, где мы рассмотрели все основные концепции.

Обратите внимание, что основные операции, которые поддерживает массив, определенным образом ограничены. Когда дело доходит до массивов, нередко можно увидеть сложные алгоритмы для выполнения относительно простых задач.

По этой причине для большинства наших операций мы будем использовать вспомогательные классы и методы, чтобы помочь нам: классArrays, предоставляемый Java, и класс ApacheArrayUtils.

Чтобы включить последний в наш проект, нам нужно добавить зависимостьApache Commons:


    org.apache.commons
    commons-lang3
    3.8.1

Мы можем проверить последнюю версию этого артефактаon Maven Central.

3. Получить первый и последний элемент массива

Это одна из наиболее распространенных и простых задач благодаря индексируемости массивов.

Начнем с объявления и инициализации массиваint, который будет использоваться во всех наших примерах (если мы не укажем иное):

int[] array = new int[] { 3, 5, 2, 5, 14, 4 };

Зная, что первый элемент массива связан со значением индекса 0 и что у него есть атрибутlength, который мы можем использовать, несложно выяснить, как мы можем получить эти два элемента:

int firstItem = array[0];
int lastItem = array[array.length - 1];

4. Получить случайное значение из массива

Используя объектjava.util.Random, мы можем легко получить любое значение из нашего массива:

int anyValue = array[new Random().nextInt(array.length)];

5. Добавить новый элемент в массив

Как мы знаем, массивы содержат фиксированный размер значений. Следовательно, мы не можем просто добавить элемент и превысить этот лимит.

Нам нужно будет начать с объявления нового, более крупного массива и скопировать элементы базового массива во второй.

К счастью, классArrays предоставляет удобный метод для репликации значений массива в новую структуру другого размера:

int[] newArray = Arrays.copyOf(array, array.length + 1);
newArray[newArray.length - 1] = newItem;

При желании, если классArrayUtils доступен в нашем проекте, мы можем использовать егоadd method  (или его альтернативуaddAll) для достижения нашей цели в однострочном выражении:

int[] newArray = ArrayUtils.add(array, newItem);

Как мы можем представить, этот метод не изменяет исходный объектarray; мы должны назначить его вывод новой переменной.

6. Вставьте значение между двумя значениями

Из-за его символа индексированных значений вставка элемента в массив между двумя другими не является простой задачей.

Apache посчитал это типичным сценарием и реализовал метод в своем классеArrayUtils для упрощения решения:

int[] largerArray = ArrayUtils.insert(2, array, 77);

Мы должны указать индекс, в который мы хотим вставить значение, и на выходе будет новый массив, содержащий большее количество элементов.

Последний аргумент является переменным аргументом (a.k.a. vararg), таким образом, мы можем вставить любое количество элементов в массив.

7. Сравнить два массива

Несмотря на то, что массивы являютсяObjects и, следовательно, предоставляют методequals, они используют его реализацию по умолчанию, полагаясь только на ссылочное равенство.

В любом случае мы можем вызвать методjava.util.Arraysequals, чтобы проверить, содержат ли два объекта массива одинаковые значения:

boolean areEqual = Arrays.equals(array1, array2);

Примечание: этот метод не эффективен дляjagged arrays. Подходящим методом проверки равенства многомерных структур является методArrays.deepEquals.

8. Проверьте, пуст ли массив

Это несложное присвоение, имея в виду, что мы можем использовать атрибут массивовlength:

boolean isEmpty = array == null || array.length == 0;

Более того, у нас также есть нулевой безопасный метод во вспомогательном классеArrayUtils, который мы можем использовать:

boolean isEmpty = ArrayUtils.isEmpty(array);

Эта функция по-прежнему зависит от длины структуры данных, которая также рассматривает нули и пустые подмассивы как допустимые значения, поэтому нам придется следить за этими крайними случаями:

// These are empty arrays
Integer[] array1 = {};
Integer[] array2 = null;
Integer[] array3 = new Integer[0];

// All these will NOT be considered empty
Integer[] array3 = { null, null, null };
Integer[][] array4 = { {}, {}, {} };
Integer[] array5 = new Integer[3];

9. Как перемешать элементы массива

Чтобы перемешать элементы в массиве, мы можем использовать функциюArrayUtil:

ArrayUtils.shuffle(array);

Это методvoid, работающий с фактическими значениями массива.

10. Box и Unbox Массивы

Мы часто встречаем методы, которые поддерживают только массивы на основеObject.

И снова вспомогательный классArrayUtils пригодится, чтобы получить коробочную версию нашего примитивного массива:

Integer[] list = ArrayUtils.toObject(array);

Обратная операция также возможна:

Integer[] objectArray = { 3, 5, 2, 5, 14, 4 };
int[] array = ArrayUtils.toPrimitive(objectArray);

11. Удалить дубликаты из массива

Самый простой способ удалить дубликаты - преобразовать массив в реализациюSet.

Как мы, возможно, знаем,Collections использует Generics и, следовательно, не поддерживает примитивные типы.

По этой причине, если мы не обрабатываем объектно-ориентированные массивы, как в нашем примере, нам сначала нужно упаковать наши значения:

// Box
Integer[] list = ArrayUtils.toObject(array);
// Remove duplicates
Set set = new HashSet(Arrays.asList(list));
// Create array and unbox
return ArrayUtils.toPrimitive(set.toArray(new Integer[set.size()]));

Примечание: мы также можем использоватьother techniques to convert between an array and a Set object.

Кроме того, если нам нужно сохранить порядок наших элементов, мы должны использовать другую реализациюSet, такую ​​какLinkedHashSet.

12. Как напечатать массив

Как и в случае с методомequals, функция массиваtoString использует реализацию по умолчанию, предоставляемую классомObject, что не очень полезно.

Оба классаArrays иArrayUtils поставляются со своими реализациями для преобразования структур данных в читаемыйString.

Помимо немного другого формата, который они используют, самое важное различие заключается в том, как они обрабатывают многомерные объекты.

Класс Java Util предоставляет два статических метода, которые мы можем использовать:

  • toString: не работает с зубчатыми массивами

  • deepToString: поддерживает любые массивы на основеObject, но не компилируется с аргументами примитивных массивов

С другой стороны,Apache’s implementation offers a single toString method that works correctly in any case:

String arrayAsString = ArrayUtils.toString(array);

13. Сопоставить массив с другим типом

Часто бывает полезно применять операции ко всем элементам массива, возможно, преобразовывая их в другой тип объекта.

С этой цельюwe’ll try to create a flexible helper method using Generics:

public static  U[] mapObjectArray(
  T[] array, Function function,
  Class targetClazz) {
    U[] newArray = (U[]) Array.newInstance(targetClazz, array.length);
    for (int i = 0; i < array.length; i++) {
        newArray[i] = function.apply(array[i]);
    }
    return newArray;
}

Если мы не используем Java 8 в нашем проекте, мы можем отказаться от аргументаFunction и создать метод для каждого сопоставления, которое нам необходимо выполнить.

Теперь мы можем повторно использовать наш универсальный метод для различных операций. Давайте создадим два тестовых примера, чтобы проиллюстрировать это:

@Test
public void whenMapArrayMultiplyingValues_thenReturnMultipliedArray() {
    Integer[] multipliedExpectedArray = new Integer[] { 6, 10, 4, 10, 28, 8 };
    Integer[] output =
      MyHelperClass.mapObjectArray(array, value -> value * 2, Integer.class);

    assertThat(output).containsExactly(multipliedExpectedArray);
}

@Test
public void whenMapDividingObjectArray_thenReturnMultipliedArray() {
    Double[] multipliedExpectedArray = new Double[] { 1.5, 2.5, 1.0, 2.5, 7.0, 2.0 };
    Double[] output =
      MyHelperClass.mapObjectArray(array, value -> value / 2.0, Double.class);

    assertThat(output).containsExactly(multipliedExpectedArray);
}

Для примитивных типов нам сначала нужно упаковать наши значения.

В качестве альтернативы мы можем обратиться кJava 8’s Streams, чтобы выполнить отображение за нас.

Сначала нам нужно преобразовать массив вStream изObjects. Мы можем сделать это с помощью методаArrays.stream.

Например, если мы хотим сопоставить наши значенияint с пользовательским представлениемString, мы реализуем это:

String[] stringArray = Arrays.stream(array)
  .mapToObj(value -> String.format("Value: %s", value))
  .toArray(String[]::new);

14. Фильтровать значения в массиве

Фильтрация значений из коллекции - это обычная задача, которую нам, возможно, придется выполнять более одного раза.

Это потому, что в то время, когда мы создаем массив, который будет получать значения, мы не можем быть уверены в его окончательном размере. Следовательно,we’ll rely on the Streams approach again.

Представьте, что мы хотим удалить все нечетные числа из массива:

int[] evenArray = Arrays.stream(array)
  .filter(value -> value % 2 == 0)
  .toArray();

16. Заключение

Массивы - одна из основных функций Java, и поэтому очень важно понимать, как они работают, и знать, что мы можем и что не можем с ними делать.

В этом уроке мы узнали, как правильно обрабатывать операции с массивами в обычных сценариях.

Как всегда, полный исходный код рабочих примеров доступен наour Github repo.